How to generate a Rc<T> from &T in rust?

Viewed 46

To learn about Weak ref in Rust, I wrote the following code. The main thing is a Factory and a Car. use a Factory to weak ref to some of the cars it produces, and these Car always ref to the Factory that produced it.

Now I want to implement a Factory.make_car() function that creates a car object that implements the above reference relationship.

But while writing this function, I ran into a difficulty. Because the parameter of make_car() is a &mut of Factory object, how can I get the Rc<Factory> object?

That is, how to modify the following ???? so that the program can compile successfully?

struct Factory {
    cars: Vec<Weak<Car>>,
}

struct Car {
    fact: Rc<Factory>,
}

impl Factory {
    pub fn make_car(&mut self) -> Car {
        let car = Car { fact: Rc::clone(???), };
        let carc = Rc::new(car);
        self.cars.push(Rc::downgrade(&carc));
        return car;
    }
}

#[cfg(test)]
pub mod tests {
    #[test]
    fn test_make_car() {
        let mut factory = super::Factory{cars: vec![]};
        let car = factory.make_car();
        
    }
}
1 Answers

In short, you can't generate an Rc<T> from &T. To get Rc<T>, you either need to call Rc::new() on a T (which will allocate the space on the heap and move T there) or clone an existing Rc (which will increment the refcount and give you a new pointer to the existing storage). Given just a &T which might not even be inside an Rc (it could be stack-allocated, or inside a struct field or in a Box or a Vec, you get the idea), it's impossible to create an Rc<T>.

What you are trying to achieve is ultimately possible, but will require significant changes in the interface and the implementation, some of which you might find uncomfortable:

  1. A Factory can only ever exist inside an Rc - otherwise a car won't be able to hold a Rc<Factory>. You can introduce a new() constructor that just returns Rc<Factory>, so you never even obtain a bare Factory.
  2. That means that, in order to modify it, you have to introduce interior mutability, i.e. change the cars field from Vec<X> to RefCell<Vec<X>>.
  3. With that in place, make_car() should accept self: &Rc<Factory>, which will allow a simple factory.make_car() to work (given that factory is only available inside Rc to begin with, ensured in #1).
  4. make_car() cannot return Car because Car was moved inside the Rc. It must return Rc<Car> instead, which also ensures that the weak reference will stay live. (Destroying Rc<Car> inside the function would immediately kill the weak reference, which is probably not what you intended.)

With the above changes applied, the code could look like this:

struct Factory {
    cars: RefCell<Vec<Weak<Car>>>,
}

struct Car {
    fact: Rc<Factory>,
}

impl Factory {
    fn new() -> Rc<Self> {
        Rc::new(Factory {
            cars: RefCell::new(vec![]),
        })
    }

    pub fn make_car(self: &Rc<Factory>) -> Rc<Car> {
        let car = Rc::new(Car { fact: self.clone() });
        self.cars.borrow_mut().push(Rc::downgrade(&car));
        return car;
    }
}

Playground

Related