Rust pattern for initialization


#1

I have a simple piece of code that basically does the following:

  • create object A
  • create object B passing A as an immutable reference as B just needs to call A at some point in its lifetime
  • loop forever calling both A.doSomething() and B.doSomething()

The problem is that A.doSomething() is also mutating A itself and since A has been immutably borrowed to B, the compiler complains about it.

I’m pretty sure there should be a pattern to follow for those situations where I have object B that needs to call into object A so that it needs a reference. What would it be, though? :slight_smile:


#2

If you’re going to pass in a reference to some dependency as part of object B’s constructor you won’t be using object A until object B gets destroyed. Doing what you are trying to do kinda goes against the way Rust wants to work because it leads to issues like race conditions and iterator invalidations, so you may want to rethink what you are doing and see if there is another way.

Otherwise there’s always Rc<RefCell<T>> or its thread-safe counterpart, Arc<Mutex<T>> (or Arc<RwLock<T>>). I tend to avoid those though because it feels more like a hack trying to usurp the borrow checker instead of fixing the problem (aliased pointers and mutation) by designing your system to be compile-time proven not to have those kinds of data race issues.


#3

As @Michael-F-Bryan said, see if you can restructure the design to avoid this. One obvious change is to pass A as an argument to those B functions that need it. Another is make B own the A and then loop over just B, which can internally mutate its A. If things like that don’t work, then interior mutability is the general Rust approach for things like this.


#4

@Michael-F-Bryan @vitalyd I pretty much follow the same philosophy. If I need to trick the borrow checker then there’s something wrong in the design of my software.

With that being said, I tried the approach where I pass A to those functions of B that need it but it’s pretty much the same problem.

Here’s a real use case where sb is the struct that is going to need canvas for some of its functions:

let mut sb = SpriteBatch::new();
'running: loop {
    canvas.set_draw_color(SdlColor::RGB(191, 255, 255));
    canvas.clear();
    {
        let position = Vector2::new(0.0, 0.0);
        let matrix: Matrix4<f32> = Matrix4::one();
        sb.begin(&mut canvas, SpriteSortMode::SpriteSortModeDeferred, Some(&shader), Some(matrix));
        for bunny in bunnies.iter_mut() {
            bunny.update();
            sb.draw(wabbit.clone(), Some(bunny.position), None, None, None, 0.0, None, Color::white(), 0.0);
        }
        sb.end(&mut canvas);
    }
}

This isn’t working as the first mutable borrow happens at sb.begin() and the second at canvas.clear().
Maybe this is one of those cases where working with an explicit lifetime might make it work. After all, I only need the first mutable borrow of canvas to get the value of one of its fields. I could immutably borrow it but then the borrow checker would complain that I’m borrowing it both mutably and immutably.
Any suggestion is appreciated. I really want to avoid resorting to RefCell or anything like that for this use case.


#5

What are the signatures of SpriteBatch::begin and end? The mutable borrows should last till the end of the statement (i.e. those begin and end calls in the loop) unless something is extending it.


#6

@vitalyd here are the signatures:

pub fn begin<'c: 'sb>(&mut self, renderer: &'c Canvas<Window>, sortMode: SpriteSortMode, shader: Option<&'sb Shader>, transformMatrix: Option<Matrix4<f32>>)
pub fn end<'c: 'sb>(&'sb mut self, renderer: &'c Canvas<Window>)

as it’s defined the 'c lifetime is outliving 'sb which is the lifetime of the SpriteBatch struct itself as defined here:

pub struct SpriteBatch<'sb>

#7

Ok, so that’s where the lifetime extension of canvas occurs. As written, the first time you call begin the canvas is mutably borrowed for the lifetime of SpriteBatch and effectively you can no longer use it mutably elsewhere.

Why do you have the lifetimes specified like that? I’m guessing it’s because you’re trying to store references somewhere inside those functions.

You mentioned that you only need one field from the canvas. That may be one way out since Rust allows mutably borrowing disjoint fields of a struct at the same time.


#8

@vitalyd you’re right. There’s no point in extending the lifetime of canvas like I did. I took that out of the equation and that’s already better.
On the other hand now I’m having pretty much the same issue with sb.begin(), sb.draw() and sb.end() all borrowing sb to mutate it (&mut self in the signature) and since they happen one right after the other the borrow checker is complaining. All three of those functions are actually mutating sb’s fields but each one is done right after the function is called. I wonder if this could be solved by turning them into something like sb = sb.begin() and so on.


#9

Are they borrowing &mut self or &'sb mut self? The end function signature in your previous post had 'sb but I’m not sure what you’ve modified since then.

Note that you rarely need a mutable borrow of self for longer than a single function call unless you’re explicitly implementing scoped borrows.


#10

You’re spot on it. I still had &'sb mut self on the end() function. I removed that and it’s no longer complaining (as I was expecting).
Super!