Automatique clone with RefCell

HI everybody,

I am learning Rust with a small numerical computation project.
I need to use some graph data structure in it. So I need multiple mutable reference of the same object. That is why I am using RefCell plus Rc as reference for my object.

Here is a toy exemple to show you the problem. In the real case it is not a float but my custom object.

use std::cell::RefCell;
use std::ops;
use std::rc::Rc;

#[derive(Debug, Clone)]
struct Ref {
    data: Rc<RefCell<f32>>,
}

impl Ref {
    fn new(data: f32) -> Ref {
        Ref {
            data: Rc::new(RefCell::new(data)),
        }
    }
}

impl ops::Add<Ref> for Ref {
    type Output = Ref;

    fn add(self, _rhs: Ref) -> Ref {
        self
    }
}

fn main() {
    let mut x = Ref::new(3.0);
    let mut y = Ref::new(4.0);

    let mut z = x+y;
    let mut h = z+x;

}

I have got the following error

error[E0382]: use of moved value: `x`
  --> main.rs:31:19
   |
27 |     let mut x = Ref::new(3.0);
   |         ----- move occurs because `x` has type `Ref`, which does not implement the `Copy` trait
...
30 |     let mut z = x+y;
   |                 --- `x` moved due to usage in operator
31 |     let mut h = z+x;
   |                   ^ value used here after move
   |

I understand the error and it makes sense with the ownership features of rust. after that I sum x and y into z, x does not have the ownership anymore.

So what I am doing so fare is the following:

let mut z = x.clone()+y.clone();
let mut h = z.clone()+x.clone();

It works; but it's really hurt code readability.

Lets say I have a more complex formula.

1

let mut z = (x+y)/(x-y) + (x+y)*(x+y);

then it becomes

2

let mut z = (x.clone()+y.clone())/(x.clone()-y.clone()) + (x.clone()+y.clone())*(x.clone()+y.clone());

And it become really hard to read.

What is the best solution to have a syntax like in the first example ?

Thanks in advance.

By the way to full code is here : rusty-grad/lib.rs at develop · samsja/rusty-grad · GitHub

Thanks in advance !

Implement Add for &Ref, instead of Ref and clone in Add::add.

1 Like

Hi, thank you for you response :grinning:

I tried and it worked indeed to use reference in the add operator.

However the whole point of defining the Ref struct that way is to have a custom smart pointer to my real struct (in the toy exemple the float). Would not it be redundant to have reference over smart pointer ? I may be missing something but it seems heavy to have a ref to a smart pointer of a struct.

But if it works as I want then I am good with it !

However I would end up defining my sum that way

let mut z = (&x+&y)/(&x-&y) + (&x+&y)*(&x+&y);

is there a way to avoid the "&" with some kind of implicit deref or something ? Or myabe define a better smart pointer for my data than my actual Ref ?

You can simply do:

let (x, y) = (&x, &y);
  • Or add a ref modifier for when you created the x and y vars:

    fn main ()
    {
        let ref x = Ref::new(3.0);
        let ref y = Ref::new(4.0);
        let ref z = x + y;
        let h = z + x;
        // …
    }
    

    And you'd simply skip using ref and instead manually put & for the rare cases where you want to move (e.g., return) the computed var (e.g., h)

3 Likes

Hi,

the let ref x = seems to be the solution !

I adapt my small toy example

use std::cell::RefCell;
use std::ops;
use std::rc::Rc;

#[derive(Debug, Clone)]
struct Ref {
    data: Rc<RefCell<f32>>,
}

impl Ref {
    fn new(data: f32) -> Ref {
        Ref {
            data: Rc::new(RefCell::new(data)),
        }
    }

    fn test<'a>(r:&'a Ref){
      
    }
}


impl<'a,'b> ops::Add<&'b Ref> for &'a Ref {
    type Output = Ref;

    fn add(self, _rhs: &'b Ref) -> Ref {
        Ref::new(3.0)
    }
}

fn main() {
    let ref x = Ref::new(3.0);
    let ref y = Ref::new(4.0);

    let  ref z = x+y;
    let  h = z+x;

    Ref::test(x);
    Ref::test(x);

}

What if I want to do something like

let z = (x+y)*(x+y) + x;

in the current setup I would have to do

let z = &(&(x+y)*&(x+y)) + x;

it is still better that previously with the clone but then is there any work around to avoid the & ?

Thanks again !

You gave a good example of what doesn't work so well. At first, I thought it'd be better to just use one Add impl to avoid type confusion, but I hadn't thought about working with intermediate results, that are immediately discarded.

In this case, you want to have 3 implementations:

  • impl Add<&Ref> for Ref
  • impl Add for Ref
  • impl Add for &Ref

EDIT: Oops, I accidentally send the message. There'll be another one, Soon™, that goes into more detail.

1 Like

If the data you store is either f32 or any other type implementing Copy, then you should almost always use Cell over RefCell.

Now, onto the implementation (assuming no change to the cell type):

impl Add<&Ref> for Ref {
    type Output = Ref;

    fn add(mut self, rhs: &Ref) -> Ref {
        /*
        `Rc` boxes its interior value and the heap allocation/deallocation may
        be expensive. Therefore, an `Rc` should be recycled whenever possible.
        */
        return match Rc::get_mut(&mut self.data) {
            Some(inner_mut) => {
                *inner_mut.get_mut() += *rhs.data.borrow();
                self
            }
            None => &self + rhs /* Delegates to `impl Add for &Ref`. */,
        };
    }
}

impl Add for Ref {
    type Output = Ref;

    fn add(self, rhs: Ref) -> Ref {
        /*
        Delegates to `impl Add<&Ref> for Ref`. This might be a bit slower than
        doing the math directly, because `Rc::get_mut` will perform the same
        check. The compiler may or may not be able to remove the duplicate check
        depending on what gets inlined and LLVM's capability.
        */
        if Rc::strong_count(&self.data) == 1 && Rc::weak_count(&self.data) == 0 {
            self + &rhs
        } else {
            rhs + &self
        }
    }
}

impl Add for &Ref {
    type Output = Ref;

    fn add(self, rhs: &Ref) -> Ref {
        Ref::new(*self.data.borrow() + *rhs.data.borrow())
    }
}

P.S.: I haven't provided impl Add<Ref> for &Ref, but it is trivial to implement.

2 Likes

Thanks,

it is definitely the good solution.

My data structure is a graph with a valu which is for now only f32 and and a reference. That is why I am using for now Rc. But I should maybe use Cell though, I begin and am not sure to really grasp the difference between the two.

Rc and Cell/RefCell solve different problems.

Rc allows your data to have multiple owners, and have the data live until all owners have dropped it, but prevents you mutating the data (otherwise you would break the aliasing rules).

Cell/RefCell provide 'interior mutability': they allow you to change the value inside of an immutable object. They do this by preventing you aliasing the value more than once, in different ways. Cell doesn't even give you a reference to its contents, just a set method that you can use to replace them, while RefCell provides a method to get a mutable reference, but will panic if you call it twice. Of course Cell could not work if your set method could be interrupted half way through and something else then calls set, which is why Cell is not thread-safe.

Another pattern that I sometimes use instead of Rc and Cell/RefCell is an enum with a dummy value, where Option is the simplest kind of enum with this property. You can then do Option::take and give a mutable reference to the Option (or the thing that contains it) to something else while you work on the value.

Cell only works with Copy types because if a type was Clone you could put arbitrary code in the clone() function which could do bad things (sorry I'm skimming over the details).

Define "redundant" and "heavy". Sometimes, you want a pointer-to-pointer-like construct, because it's necessary or more convenient or more efficient. Also, references don't have any runtime overhead, if that's what you are worried about.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.