Help figuring this out... (beginner alert)

Hi! I came from C#/Kotlin/Java/ES and such. Really like Rust, but some things I yet to understand. Guess I need to make my brains work a little different than I used to for all those years :slight_smile:

I have a simple code here that compiles and runs

struct Controller<'a> {
    environment: &'a Environment<'a>,
    something: &'static str
}

impl<'a> Controller<'a> {
    fn new(env: &'a Environment) -> Self{
        Controller{
            environment: env,
            something: "abcdef"
        }
    }
    
    fn do_smt(&self){
        println!("from controller itself: {}", self.something)
    }
}

struct Environment<'a> {
    wind_speed: u64,
    game_time: &'a mut GameTime
}

impl<'a> Environment<'a> {
    fn new(wind_speed: u64, game_time: &'a mut GameTime) -> Self{
        Environment{
            wind_speed,
            game_time
        }
    }
    
    fn change_wind_speed(&mut self, value: u64){
        self.wind_speed = value
    }
    
    fn set_game_time(&mut self, new_time: &'a mut GameTime){
        self.game_time = new_time
    }
    
    fn do_smt(&self){
        println!("from env itself: {}", self.wind_speed)   
    }
}

struct GameTime {
    day: u64
}

impl GameTime {
    fn new(day: u64) -> Self{
        GameTime{
            day
        }
    }
    
    fn change_day(&mut self, value: u64){
        self.day = value
    }
    
}

fn main() {
    let mut gt = GameTime::new(3);
    let mut env = Environment::new(6, &mut gt);
    let c = Controller::new(&env);
    let newgt = &mut GameTime::new(222);

    println!("{0}, {1}", env.wind_speed, env.game_time.day);
    
    env.change_wind_speed(5);
    env.game_time.change_day(10);
    
    println!("{0}, {1}", env.wind_speed, env.game_time.day);
    
    env.set_game_time(newgt);
    
    println!("{0}, {1}", env.wind_speed, env.game_time.day);
    
    //c.do_smt();
    env.do_smt();
}

... that's until I uncomment the "c.do_smt();" line at the end. Why can't I call a method that does not mutate anything on that object? Even thou exactly same "env.do_smt();" does what I expect it to do?

Sorry for noob questions, but I really tried do google something out)

The problem is that a mutable reference must have exclusive access, and env.change_wind_speed takes a mutable reference, so to call that, you must have exclusive access to env at that point. It fails to compile because you do not have exclusive access as there is another reference to env inside c.

The reason it works without the c.do_smt() call is that the compiler can see you don't use c anywhere, and it has no destructor, so the compiler will shorten where c exists by making it go out of scope immediately after construction. Since it has made c go out of scope before you call env.change_wind_speed, there's no problem and you have exclusive access to env.

But calling c.do_smt() prevents the compiler from making c go out of scope early, hence the error.

Thank you for reply, alice!
Exclusive access... so I cannot make a pyramid of objects and have their pointers floating around in any place I want to change them, they need to be altered only where they were created basically (I cannot just grab a pointer to some object.subobject1.subobject2 and change its internals just like that).

But I cannot instatiate an object inside "new", because it is a "value referencing temporary value", it has to come from outside...

How can I implement in Rust this not very complicated task then?
Have an object1 that receives another object2 in a constructor and stores it to some public field for future access from outside.
Object2 is an object that receives in its constructor another object3 and stores it to some public field for future access from outside.
Be able to change any property in object1, object2 and object3 from outside when program logic needs to do that.
Object1, object2 and object3 have some methods that change something inside them and methods that just do something without altering anything. Be able to call those methods from the scope where object1,2 and 3 do exist, when program logic needs to.

No scary cross references, nothing fancy, just linear chain of objects.

I'll be sooo happy to see such example!

Generally Rust has two kinds of references: shared and exclusive references. We usually call them immutable and mutable, but their true nature is whether they allow shared access or not.

There are ways to mutate objects through shared access. This is called interior mutability. You wrap the fields you want to be modifiable from shared access in one of the cell types:

The Cell type is usually best suited for types that are Copy, whereas RefCell can be used for more complicated types. Then, once the values are wrapped in cells, you can mutate through immutable shared references.

For example, if you change wind_speed to the type Cell<u64>, you can do this:

// note &self instead of &mut self
fn change_wind_speed(&self, value: u64){
    self.wind_speed.set(value);
}

Note also that you may want to replace all of your references with Rc. This is because with lifetimes, the existence of a reference does not keep the object it points at alive — the compiler just verifies that the scopes are nested correctly in a way such that the objects live long enough, and fails to compile if this is not the case. By using Rc instead, any reference to the value will keep the value alive, allowing you to remove all the lifetimes from your types.

Note that one gotcha is that if you build a cycle of Rc objects, then that's a memory leak because the Rcs in the cycle all keep each other alive.

Anyway, you might write e.g. this:

use std::cell::Cell;
use std::rc::Rc;

// No lifetime!
struct Environment {
    wind_speed: Cell<u64>,
    game_time: Rc<GameTime>,
}

struct GameTime {
    day: Cell<u64>,
}

Here, instead of using an &mut, we use an Rc. The Rc gives shared access to the game_time, and the Cell inside GameTime allows shared mutation of the day field.

Note that the Rc type goes instead of references. You are not supposed to combine them into e.g. Rc<&mut GameTime> or &mut Rc<GameTime> or something like that. An Rc is a kind of smart pointer, so you can access the object stored inside it directly, e.g. if GameTime has a method foo, you can call it on Rc<GameTime> just with the_rc_gametime.foo().

The way you obtain multiple Rc handles to the same object is by calling clone. When you do an_rc.clone(), this only clones the Rc, giving a new handle to the same object, and mutations through one handle are visible through the other.

Some relevant resources:

1 Like

That's awesome, so much info!
Well then, 'll be reading and trying, trying and reading again)

I thought like in C I'll grab my pointers and do whatever with them :smiley:

Thank you for the explanation and pointing to all these resources!

The other option for getting away from interior mutability is to separate things out as mutable only when they absolutely need to be. In terms of a runtime/environment, you can start with a full mutable version, go through all your setup, loading data, etc. and then use either Into<ImEnvironment> or ImEnvironment::from(env: &Environment) to create a static copy of all its data that can be referenced by as many objects/threads as you want for the actual runtime.

let mut env = Environment::new();
/* setup, load data, etc. */
let controller_env = ImEnvironment::from(&env); // Now fully shareable

Then if you still need certain portions of the environment to be mutable you can create smaller sub-structs for each purpose. Thus if you want to change wind speed, you only need mutable access to one small bit, rather than the entire top-level struct. You can even give each sub struct it's own &ImEnvironment reference. Your controller might then look like:

pub struct Controller<'env> {
    pub env: &'env ImEnvironment, 
    pub wind: WindProperties<'env>,
}

pub struct WindProperties<'env> {
    pub environment: &'env ImEnvironment, // You could avoid this reference
    // if you implement top-level 'control' methods on the mutable Controller.
    pub speed: u64,
}

You could even go one step further and give your mutable Environment struct a spawn_controller() function which does everything and returns a tuple with immutable ImEnvironment and the single packaged mutable Controller.

1 Like

Interesting. Thanks, I'll look into it also )

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.