3

The following code compiles and runs but emits a mutable_borrow_reservation_conflict warning.

My goal is to have a field all_ops owning a set of Op's implementations (readonly) where each op can be referenced in another container in the same struct (and when the main all_ops container is cleared, used_ops access becomes illegal as expected)

Of course, one could use Rc but it causes performance issues.

Do you have an idea to do that properly ? (i.e. a way which will not become an hard error in the (near?) future).

trait Op {
    fn f(&self);
}

struct OpA;


impl Op for OpA {
    fn f(&self) {
        println!("OpA");
    }
}

struct OpB;

impl Op for OpB {
    fn f(&self) {
        println!("OpB");
    }
}

struct Container<'a> {
    all_ops: Vec<Box<dyn Op>>,
    used_ops: Vec<&'a Box<dyn Op>>, // data pointing to data in all_ops field
}

fn main() {
    let v: Vec<Box<dyn Op>> = vec![Box::new(OpA), Box::new(OpB)];

    let mut c = Container { all_ops: v, used_ops: Vec::new() };
    c.used_ops.push(&c.all_ops.get(0).unwrap());
    c.used_ops.push(&c.all_ops.get(1).unwrap());
    c.used_ops.push(&c.all_ops.get(0).unwrap());
    for op in c.used_ops {
        op.f();
    }
    c.all_ops.clear();
    // c.used.first().unwrap().f(); // cannot borrow `c.all` as mutable because it is also borrowed as immutable
}

Rust playground

Pascal H.
  • 1,431
  • 8
  • 18
  • Of course, this problem could be circumvented by referencing the *ops* of `all_ops` used in `used_ops` with integers but then the two containers would be semantically disjoint and the compiler would not be able to detect that the modifications of `all_ops` are illegal (because already shared with immutable borrows). So I'm looking for a typesafe, type-driven solution. – Pascal H. Jan 22 '22 at 06:46

1 Answers1

0

If I replace used_ops: Vec<&'a Box<dyn Op>> by used_ops: Vec<&'a dyn Op>, it seems sufficient to fix the warning.

Unfortunately, Container isn't movable even if all references in used_ops are on objects allocated in the heap (and I understand why since there are references to (inner parts of) the object).

trait Op {
    fn f(&self);
}

struct OpA;


impl Op for OpA {
    fn f(&self) {
        println!("OpA");
    }
}

struct OpB;

impl Op for OpB {
    fn f(&self) {
        println!("OpB");
    }
}

struct Container<'a> {
    all_ops: Vec<Box<dyn Op>>,
    used_ops: Vec<&'a dyn Op>, // data pointing to data in all_ops field
}

fn main() {
    let v: Vec<Box<dyn Op>> = vec![Box::new(OpA), Box::new(OpB)];

    let mut c = Container { all_ops: v, used_ops: Vec::new() };
    c.used_ops.push(c.all_ops.get(0).unwrap().as_ref());
    c.used_ops.push(c.all_ops.get(1).unwrap().as_ref());
    c.used_ops.push(c.all_ops.get(0).unwrap().as_ref());
    for op in c.used_ops.iter() {
        op.f();
    }
    // let c2 = c; // cannot move out of `c` because it is borrowed
}

Playground

Pascal H.
  • 1,431
  • 8
  • 18
  • A related question could be: is it a definite limitation of the Rust's type system? (is necessary, I will open another thread). – Pascal H. Jan 25 '22 at 10:56
  • 1
    See https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct or store indices instead of references? – Solomon Ucko Jan 25 '22 at 11:55
  • I just read https://doc.rust-lang.org/std/pin/#example-self-referential-struct and came back to quote it here and saw your link. That's exactly what I understood there and the Stackflow answer https://stackoverflow.com/a/32300133/12430075 is extremely educational, perfect. – Pascal H. Feb 13 '22 at 16:05