Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

General pointer type for `Rc`, `Box`, `Arc`

Tags:

rust

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.

like image 591
Kapichu Avatar asked Jul 05 '15 11:07

Kapichu


People also ask

Is Box A pointer in Rust?

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 .

What is ARC for Rust?

'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.

How do you use boxes in Rust?

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.


2 Answers

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)
    }
}
like image 102
Francis Gagné Avatar answered Oct 20 '22 08:10

Francis Gagné


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.

like image 1
DK. Avatar answered Oct 20 '22 08:10

DK.