4

Foo can be modified using the method .modify():

struct Foo;
impl Foo {
    fn modify(&mut self) {}
}

Bar stores a callback:

struct Bar<'a> {
    callback: Box<FnMut() + 'a>,
}
impl<'a> Bar<'a> {
    fn new<F: FnMut() + 'a>(f: F) -> Bar<'a> {
        Bar {
            callback: Box::new(f),
        }
    }
}

init() takes a slice of Bar and executes their callbacks:

fn init(bars: &mut [Bar]) {
    for b in bars {
        (*b.callback)();
    }
}

And now the most interesting:

Changing Foo in a loop works fine; on each iteration of the loop foo is mutably borrowed and .modify() is called:

fn main() {
    let mut foo = Foo;

    for _ in 0..10 {
        foo.modify();
    }
}

Changing Foo inside of the callbacks does not work:

fn main() {
    let mut foo = Foo;

    let mut bar1 = Bar::new(|| foo.modify());
    let mut bar2 = Bar::new(|| foo.modify());

    init(&mut [bar1, bar2]);
}

Try it on the playground, it has an error:

error[E0499]: cannot borrow `foo` as mutable more than once at a time
  --> src/main.rs:27:29
   |
26 |     let mut bar1 = Bar::new(|| foo.modify());
   |                             -- --- previous borrow occurs due to use of `foo` in closure
   |                             |
   |                             first mutable borrow occurs here
27 |     let mut bar2 = Bar::new(|| foo.modify());
   |                             ^^ --- borrow occurs due to use of `foo` in closure
   |                             |
   |                             second mutable borrow occurs here
...
30 | }
   | - first borrow ends here

How to implement a similar guarantee for item 2?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Zibaha
  • 41
  • 2

1 Answers1

4

You can use RefCell:

let foo = RefCell::new(Foo);

{
    let bar1 = Bar::new(|| foo.borrow_mut().modify());
    let bar2 = Bar::new(|| foo.borrow_mut().modify());
    init(&mut [bar1, bar2]);
}

let mut foo = foo.into_inner(); // extract foo to use in external API

Be careful with borrow_mut(), it panics if the value is currently borrowed.


If you can change Bar and init(), you can pass value foo to the init() separate from the method modify():

struct Bar<'a> {
    callback: Box<FnMut(&mut Foo) + 'a>,
}
impl<'a> Bar<'a> {
    fn new<F: FnMut(&mut Foo) + 'a>(f: F) -> Bar<'a> {
        Bar {
            callback: Box::new(f),
        }
    }
}

fn init(bars: &mut [Bar], arg: &mut Foo) {
    for b in bars {
        (*b.callback)(arg);
    }
}
let mut bar1 = Bar::new(|x| x.modify());
let mut bar2 = Bar::new(Foo::modify); // you can pass it without closure
init(&mut [bar1, bar2], &mut foo);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
aSpex
  • 4,790
  • 14
  • 25
  • The problem is that `Foo` is an public user API. – Zibaha Jun 25 '16 at 10:57
  • You do not need to modify `Foo`, only closures - arguments of the `Bar::new()` – aSpex Jun 25 '16 at 11:01
  • But `Foo` will be used in other places, to get ref need write `&foo.borrow()` - that's a lot. To solve the problem, may using `Fn`, not `FnMut`. Then `RefCell` will be within `Foo`, but all methods must be called using `&self` - this crutch will be visible in the user API..... – Zibaha Jun 25 '16 at 11:15
  • The second example is not suitable, because will be other objects that will need to be edited in callbacks. I think that the function signature is `fn bar(pattern: Foo, ..)`, i.e. `struct Foo(u32); fn bar(Foo(x): Foo) { println!("{}", x) }` is completely correct. But in the method syntax `self` is reserved. All of the following are true: `fn bar(self: Self)`, `fn bar(self: &mut Self)`, `fn bar(mut self: &mut Self)`, `fn bar(self: Box)`... Why work with `Box`? Maybe to this pattern will turn unsafe magic? Or hide `RefCell`? – Zibaha Jun 25 '16 at 13:45
  • In method syntax `&mut` self makes borrowing. The std has a trait `std::borrow::BorrowMut`, which theoretically is possible to implement, but I failed spoof lifetime. – Zibaha Jun 25 '16 at 14:25
  • It seems I can do nothing more to help. You would be better to create a new code example that better reflects your problem. Or create a new question linked to this. – aSpex Jun 25 '16 at 16:46