Passing a mutable reference based on "self"


#1

I am trying to pass a object (SceneManager) as a to a trait but I can’t figure out what the borrow checker is telling me I need to do. I can get around it by using Rc-RefCells but I am trying to accomplish it using references. I only want to use Cells when I need to.

Basically I want a Node to callback on a method of the SceneManager (aka pause()). The problem is I can’t figure out the lifetime attributes in order to pass the SceneManager to the node’s step() method.

Any ideas? Thanks.

trait NodeTrait<'a> {
    fn get_x(&self) -> u32;
    fn set_x(&mut self, x: u32);
    fn step(&mut self, scm: &'a mut SceneManager);
    fn print(&self);
}

#[derive(Debug)]
struct Node {
    x: u32,
}

impl Node {
    fn new() -> Self {
        Self {
            x: 0,
        }
    }
}

impl<'a> NodeTrait<'a> for Node {
    fn get_x(&self) -> u32 {
        self.x
    }
    
    fn set_x(&mut self, x: u32) {
        self.x = x;
    }
    
    fn step(&mut self, scm: &'a mut SceneManager) {
        scm.pause(0);
    }
    
    fn print(&self) {
        println!("{}", self.x);
    }
}

struct SceneManager<'a> {
    nodes: Vec<&'a NodeTrait<'a>>,    
}

impl<'a> SceneManager<'a> {
    fn new() -> Self {
        Self {
            nodes: Vec::new(),
        }
    }
    
    fn add(&mut self, n: &'a (NodeTrait<'a> + 'a)) {
        self.nodes.push(n);
    }
    
    fn step(&mut self) {
        for node in &self.nodes {
            node.step(self);
        }
    }
    
    fn pause(&mut self, x: u32) {
        for node in &self.nodes {
            if node.get_x() == x {
                println!("pausing {}", x);
            }
        }
    }
    
    fn print(&self) {
        for node in &self.nodes {
            node.print();
        }
    }
}

struct World<'a> {
    scm: SceneManager<'a>,
}

impl<'a> World<'a> {
    fn new() -> Self {
        Self {
            scm: SceneManager::new(),
        }
    }
    
    fn add(&mut self, node: &'a (NodeTrait<'a> + 'a)) {
        self.scm.add(node);
    }
    
    fn step(&mut self) {
        self.scm.step();
    }
    
    fn print(&self) {
        self.scm.print();
    }
}

fn main() {
    let mut world = World::new();
    
    let n1 = Node::new();
    let mut n2 = Node::new();
    n2.set_x(2);
    
    world.add(&n1);
    world.add(&n2);
    
    world.print();
    
    world.step();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/main.rs:56:23
   |
56 |             node.step(self);
   |                       ^^^^
   |
note: ...the reference is valid for the lifetime 'a as defined on the impl at 43:6...
  --> src/main.rs:43:6
   |
43 | impl<'a> SceneManager<'a> {
   |      ^^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 54:5
  --> src/main.rs:54:5
   |
54 | /     fn step(&mut self) {
55 | |         for node in &self.nodes {
56 | |             node.step(self);
57 | |         }
58 | |     }
   | |_____^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0312`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.


#2

NodeTrait::step should have its own lifetime parameter. Like this:

trait NodeTrait {
  fn step<'a>(&mut self, scm: &'a mut SceneManager);
}

The version you have asserts that the lifetime 'a has to live as long as the Node itself, while this alternative merely asserts that 'a lives as long as the function call.

NodeTrait itself doesn’t need to have a lifetime parameter (though SceneManager still need one, for that Vec<&'a NodeTrait> it contains).


#3

Thanks notriddle.

That fixed it but now I run into a problem I have seen before dealing with iterating vec's. The iteration borrows-out an element which prevent me from making a mutable call on the element:

    fn step(&mut self) {
        for node in &self.nodes {   <== Borrows here
            node.step(self);    <== fails compiling here
        }
    }

This seems like a catch-22. How do I get an element without a borrow penalty already applied?

error[E0596]: cannot borrow `**node` as mutable, as it is behind a `&` reference
  --> src/main.rs:56:13
   |
56 |             node.step(self);
   |             ^^^^ cannot borrow as mutable

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:56:13
   |
55 |         for node in &self.nodes {
   |                     -----------
   |                     |
   |                     immutable borrow occurs here
   |                     immutable borrow used here, in later iteration of loop
56 |             node.step(self);
   |             ^^^^^^^^^^^^^^^ mutable borrow occurs here

#4

You cannot do this.

  1. node.step requires that node be mutable, but because it is behind a shared reference, it cannot be borrowed mutably.
  2. even if you used &mut self.nodes it would still not work because you would have overlapping mutable references (these references must never alias)
    2.1. One reason this rule exists is because of this: What if you try and use SceneManager::push while in a node in while iterating in SceneManager::step?

You will have to restructure your code so that the NodeTrait cannot access the node list directly.


#5

Storing non-static references into struct is generally not a good idea, especially for non-temporal struct like iterator. Storing Box<dyn NodeTrait> or even Node directly to your SceneManager will greatly simplify your lifetime issue, if not totally eliminate it.


#6

Thanks Hyeonu,

I already have a solution using Heap like objects, for example Rc-RefCells, and I am not particularly keen on using static objects. I was trying to create a solution using just references.


#7

Thanks KrishnaSannasi,

I am well aware of the borrow rules and why it is happening. My question was how to correct it. I already know, and have, a solution using heap objects (aka Rc-RefCell) but was trying not to rely on them like a crutch.

You are correct in that I will need to restructure the code though I just don’t know quite how to go about doing yet. :wink:


#8

One way would be to give immutable access to the NodeTrait and have it generate a diff. Then you can apply the diff later.


#9

After all, if you store reference of Nodes to your World, those references should never outlive the owned value. But you declared Nodes after the World, so compiler will reject this code to compile as otherwise it’s possible to allow use-after-free bug.

Rust as a language is very strict at ownership tracking, shared vs exclusive(mutable) access to achieve compiler-proven memory safety.