Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve shared ownership guide #25115

Closed
cburgdorf opened this issue May 5, 2015 · 7 comments
Closed

Improve shared ownership guide #25115

cburgdorf opened this issue May 5, 2015 · 7 comments

Comments

@cburgdorf
Copy link
Contributor

I was looking through the shared ownership guide and stumbled over something that wasn't quite obvious to me at first sight.

So there is this code:

use std::rc::Rc;

struct Car {
    name: String,
}

struct Wheel {
    size: i32,
    owner: Rc<Car>,
}

fn main() {
    let car = Car { name: "DeLorean".to_string() };

    let car_owner = Rc::new(car);

    for _ in 0..4 {
        Wheel { size: 360, owner: car_owner.clone() };
    }
}

It then states:

We wrap our Car in an Rc, getting an Rc, and then use the clone() method to make new references. We've also changed our Wheel to have an Rc rather than just a Car.

So I wanted to see if changing the car has any effect on the car that's shared among the wheels.

use std::rc::Rc;

#[derive(Debug)]
struct Car {
    name: String,
}

#[derive(Debug)]
struct Wheel {
    size: i32,
    owner: Rc<Car>,
}

fn main() {
    let mut car = Car { name: "DeLorean".to_string() };

    //this copies car into the Rc which means 
    //the car that is wrapped in the RC is not the same as our car variable
    let car_owner = Rc::new(car);

    let mut wheels = vec!();

    for _ in 0..4 {
        wheels.push(Wheel { size: 360, owner: car_owner.clone() });
    }

    // I wanted to see if I can change the name of the car here
    car.name = "foo".to_string();

    //...and if it then causes to print "foo" for the car names here 
    println!("{:?}", wheels);
}

But this doesn't work. At first I thought Rc::new(car) will return a ref counted pointer to car. But that's not the case. Instead it actually makes a copy of car before it creates the ref counted pointer.

We can make this even more obvious when we use RefCell.

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

#[derive(Debug)]
struct Car {
    name: String,
}

#[derive(Debug)]
struct Wheel {
    size: i32,
    owner: Rc<RefCell<Car>>,
}

fn main() {
    let mut car = Car { name: "DeLorean".to_string() };

    let car_owner = Rc::new(RefCell::new(car));

    let mut wheels = vec!();

    for _ in 0..4 {
        wheels.push(Wheel { size: 360, owner: car_owner.clone() });
    }

    //doesn't have any effect on car_owner
    //car.name = "Porsche".to_string();

    //manipulation needs to be made on car_owner
    car_owner.borrow_mut().name = "Porsche".to_string();

    println!("{:?}", wheels);
}

So my point is that there is no connection to car anymore. car becomes pretty much useless in the scenario as it's implicitly copied with the Rc::new(car) call and from then on only car_owner holds the relevant car.

As I said, it's probably obvious to the experienced Rust user but may not for the beginner.

I would suggest to either point it out in the text more explicitly or change the example to inline the creation of the car to not keep it in a local car variable.

    let car_owner = Rc::new(Car { name: "DeLorean".to_string() });

//cc @steveklabnik

@Ryman
Copy link
Contributor

Ryman commented May 7, 2015

This example actually shows some weird behavior, I was expecting a compiler error upon mentioning car again, as it should have been moved when placed in the Rc (not exactly a copy).

It correctly errors when trying to read the value though, as seen by adding println!("{:?}", car) to the examples.

@steveklabnik
Copy link
Member

What is the "shared ownership guide"? I think this text is from the API docs of Rc, yes?

@cburgdorf
Copy link
Contributor Author

@cburgdorf
Copy link
Contributor Author

@Ryman weird indeed. Inserting println!("{:?}", car) fails with

src/main.rs:29:22: 29:25 error: use of moved value: `car`
src/main.rs:29     println!("{:?}", car)

whereas the car.name = "foo".to_string(); compiles just fine ;)

What's going on here?

@mbrubeck
Copy link
Contributor

mbrubeck commented May 8, 2015

It's legal to write to a moved-away value, but not to read from it. See #21232 for more discussion of this.

@cburgdorf
Copy link
Contributor Author

@mbrubeck thank you for the pointers! (no pun intended)

Back to the topic of improving the guide. The guide comes after the section of borrowing and lifetimes so that knowledge is already covered.

So when it says

In all the examples we've considered so far, we've assumed that each handle has a singular owner. But sometimes, this doesn't work. Consider a car. Cars have four wheels. We would want a wheel to know which car it was attached to.

We may just think, why not change it so that each wheel holds a reference to the car:

#[derive(Debug)]
struct Car {
    name: String,
}

#[derive(Debug)]
struct Wheel<'a> {
    size: i32,
    owner: &'a Car,
}

fn main() {
    let car = Car { name: "DeLorean".to_string() };

    let mut wheels = vec!();

    for _ in 0..4 {
        wheels.push(Wheel { size: 360, owner: &car });
    }
}

I think it's worth pointing out the differences to what is done with the Rc<Car> version.

@steveklabnik
Copy link
Member

This whole bit is gone now, so this issue is effectively closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants