In C++ it would something like struct A
is composed of struct B
and some function of B
takes a pointer to the parent object A
. So function of A
calling that function of B
will simply pass the this
pointer to it. I'm trying this in Rust but failing to get it to work - this is what I want to achieve:
struct A<Type: T> {
composition: Type,
value: usize,
}
impl<Type> A<Type> where Type: T {
fn new(obj: Type) -> A<Type> {
A {
composition: obj,
value: 999,
}
}
fn f(&mut self) {
println!("Value: {:?}", self.value);
}
fn call(&mut self) {
self.composition.f(&mut self);
}
}
trait T {
fn f(&mut self, &mut A<Self>);
}
struct B {
value: usize,
}
impl B {
fn new() -> B {
B { value: 0, }
}
}
impl T for B {
fn f(&mut self, parent: &mut A<B>) {
println!("B::f");
parent.f();
}
}
fn main() {
let objA = A::new(B::new());
// I want call sequence -> A::call() -> B::f() -> A::f()
objA.call();
}
Note that i require mutability in all the functions although in example above it might seem that &mut self
in most function parameters don't make much sense. How do it do this in Rust?
This cannot work because you're violating mutable aliasing requirements - you're trying to mutably borrow A
and its substructure at the same time:
self.composition.f(self);
// roughtly equivalent to:
let c = &mut self.composition; // borrow substructure
c.f(self /* borrow self */);
(I've removed explicit &mut self
because it is incorrect (as it gives you &mut &mut A<...>
, but it does not change the whole picture at all.)
This is a natural error in Rust framework. Suppose that f
implementation on this particular composition X
rewrites composition
field on the passed object:
impl T for X {
fn f(&mut self, a: &mut A<X>) {
a.composition = create_x_somehow();
}
}
And suddenly the object this method is called on is destroyed, and self
is invalidated!
Naturally, the compiler prevents you from doing this even if you know that you don't modify composition
, because such kind of knowledge cannot be encoded statically (especially given that this is a trait method which can be implemented by anyone having access to your trait).
You have essentially two choices in such situations:
- reformulate the problem so it does not require such architecture anymore, or
- use special language/library constructs to work around such static checks.
The second point is about using such things as Cell
/RefCell
(they are safe, i.e. don't require unsafe
blocks, but they can panic at runtime - probably these can work in your case) or, if nothing else helps, dropping to raw pointers and unsafe
code. But, frankly, the first option usually is better: if you design your code based on ownership semantics and aliasing rules enforced by the compiler, almost always the resulting architecture would be of much better quality.