Passing self as a parameter


#1

Day 4 with Rust and I finally stuck with a slightly more complex infrastructure.

Problem:
Over the last years I am mostly writing Java and Javascript code where a common pattern is to pass this as a reference to a new object.

final class B {
    final class A {
        final B b;

        A(final B b) {
            this.b = b;
        }
    }

    final A a;

    B() {
        a = new A(this);
    }
}

It enables A to talk to B in its entire lifetime. I could not find a proper translation for Rust. To move on I am now trying to pass this (self) in each method call from B to A to let A communicate back to B.

struct A();
struct B { a: A }
impl A {
    fn foo(&mut self, _: &B) {}
}
impl B {
    fn foo(&mut self) {
        self.a.foo(self);
    }
}
pub fn main() {
    let mut x: B = B { a: A() };
    x.foo();
}

This comes with an understandable compile error:

error[E0502]: cannot borrow self as immutable because self.a is also borrowed as mutable
–> src/examples/lifetimes.rs:11:21
|
11 | self.a.foo(&self);
| ------ ^^^^- mutable borrow ends here
| | |
| | immutable borrow occurs here
| mutable borrow occurs here

My question is:
What is the best practice for situations like this?


#2

Your best bet is to redesign to not need this pattern in the first place. Rust hates cyclic pointer graphs, and if you go down this road, you’re probably going to spend a lot of time fighting Rust.

You could pass just the parts of B that A needs, rather than the whole thing, assuming that’s possible. In some cases, it’s better to (effectively) replace instance methods with free functions that just take all the things they need to work as explicit parameters.

If you really can’t live without it, you’ll need to switch to using some combination of shared ownership and (probably) interior mutability, like Rc<RefCell<B>>, or Arc<Mutex<B>>, the specific of which depend on your exact needs. But, all in seriousness: trying to write Java in Rust is just going to lead to pain, misery, and suffering.


#3

It is interesting to note that the Java code is problematic, in the first place: the constructor B leaks its this reference to the outside world.

This is a problem, because at this point, this is not a fully constructed object, since its constructor has not yet ended. This can lead to subtle and hard-to-find bugs in the future, for example if you add a method call on b (the partial object) in the constructor A.


#4

@olivren Yes, it can be a nasty when you are not aware of this. But you quickly get used to it and avoid calling methods or access fields in the constructor. But I see now more clearly how Rust maintains safety here as well.