I have a struct
which references a value (because it is ?Sized
or very big). This value has to live with the struct, of course.
However, the struct shouldn't restrict the user on how to accomplish that. Whether the user wraps the value in a Box
or Rc
or makes it 'static
, the value just has to survive with the struct. Using named lifetimes would be complicated because the reference will be moved around and may outlive our struct
. What I am looking for is a general pointer type (if it exists / can exist).
How can the struct make sure the referenced value lives as long as the struct lives, without specifying how?
Example (is.gd/Is9Av6):
type CallBack = Fn(f32) -> f32;
struct Caller {
call_back: Box<CallBack>,
}
impl Caller {
fn new(call_back: Box<CallBack>) -> Caller {
Caller {call_back: call_back}
}
fn call(&self, x: f32) -> f32 {
(self.call_back)(x)
}
}
let caller = {
// func goes out of scope
let func = |x| 2.0 * x;
Caller {call_back: Box::new(func)}
};
// func survives because it is referenced through a `Box` in `caller`
let y = caller.call(1.0);
assert_eq!(y, 2.0);
Compiles, all good. But if we don't want to use a Box
as a pointer to our function (one can call Box
a pointer, right?), but something else, like Rc
, this wont be possible, since Caller
restricts the pointer to be a Box
.
let caller = {
// function is used by `Caller` and `main()` => shared resource
// solution: `Rc`
let func = Rc::new(|x| 2.0 * x);
let caller = Caller {call_back: func.clone()}; // ERROR Rc != Box
// we also want to use func now
let y = func(3.0);
caller
};
// func survives because it is referenced through a `Box` in `caller`
let y = caller.call(1.0);
assert_eq!(y, 2.0);
(is.gd/qUkAvZ)
Possible solution: Deref
? (http://is.gd/mmY6QC)
use std::rc::Rc;
use std::ops::Deref;
type CallBack = Fn(f32) -> f32;
struct Caller<T>
where T: Deref<Target = Box<CallBack>> {
call_back: T,
}
impl<T> Caller<T>
where T: Deref<Target = Box<CallBack>> {
fn new(call_back: T) -> Caller<T> {
Caller {call_back: call_back}
}
fn call(&self, x: f32) -> f32 {
(*self.call_back)(x)
}
}
fn main() {
let caller = {
// function is used by `Caller` and `main()` => shared resource
// solution: `Rc`
let func_obj = Box::new(|x: f32| 2.0 * x) as Box<CallBack>;
let func = Rc::new(func_obj);
let caller = Caller::new(func.clone());
// we also want to use func now
let y = func(3.0);
caller
};
// func survives because it is referenced through a `Box` in `caller`
let y = caller.call(1.0);
assert_eq!(y, 2.0);
}
Is this the way to go with Rust? Using Deref
? It works at least.
Am I missing something obvious?
This question did not solve my problem, since the value is practically unusable as a T
.
All values in Rust are stack allocated by default. Values can be boxed (allocated on the heap) by creating a Box<T> . A box is a smart pointer to a heap allocated value of type T .
'Arc' stands for 'Atomically Reference Counted'. The type Arc<T> provides shared ownership of a value of type T , allocated in the heap. Invoking clone on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc , while increasing a reference count.
Box is a very convenient type in Rust. When you use a Box , you can put a type on the heap instead of the stack. To make a new Box , just use Box::new() and put the item inside. This is why Box is called a "smart pointer", because it is like a & reference (a kind of pointer) but can do more things.
While Deref
provides the necessary functionality, AsRef
and Borrow
are more appropriate for this situation (Borrow
more so than AsRef
in the case of a struct). Both of these traits let your users use Box<T>
, Rc<T>
and Arc<T>
, and Borrow
also lets them use &T
and T
. Your Caller
struct could be written like this:
use std::borrow::Borrow;
struct Caller<CB: Borrow<Callback>> {
callback: CB,
}
Then, when you want to use the callback
field, you need to call the borrow()
(or as_ref()
) method:
impl<CB> Caller<CB>
where CB: Borrow<Callback>
{
fn new(callback: CB) -> Caller<CB> {
Caller { callback: callback }
}
fn call(&self, x: f32) -> f32 {
(self.callback.borrow())(x)
}
}
It crashes with the current stable compiler (1.1), but not with beta or nightly (just use your last Playpen link and change the "Channel" setting at the top). I believe that support for Rc<Trait>
was only partial in 1.1; there were some changes that didn't make it in time. This is probably why your code doesn't work.
To address the question of using Deref
for this... if dereferencing the pointer is all you need... sure. It's really just a question of whether or not the trait(s) you've chosen support the operations you need. If yes, great.
As an aside, you can always write a new trait that expresses the exact semantics you need, and implement that for existing types. From what you've said, it doesn't seem necessary in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With