rustcompiler-errorsborrow-checkermutation

Why does mutation through a trait object fail but mutation through a function pointer work?


I have a Rust program with two versions of structs and their method implementations. The first version uses a function pointer, while the second uses a boxed trait object. Here is the code:

pub struct FnWrapper1 {
    pub f: fn(&mut BoolAndFns1),
}

pub struct BoolAndFns1 {
    pub b: bool,
    pub fs: Vec<FnWrapper1>,
}

impl BoolAndFns1 {
    pub fn meth(&mut self) {
        if let Some(wrapper) = self.fs.last_mut() {
            (wrapper.f)(self);
        }
    }
}

pub struct FnWrapper2 {
    pub f: Box<dyn Fn(&mut BoolAndFns2)>,
}

pub struct BoolAndFns2 {
    pub b: bool,
    pub fs: Vec<FnWrapper2>,
}

impl BoolAndFns2 {
    pub fn meth(&mut self) {
        if let Some(wrapper) = self.fs.last_mut() {
            (wrapper.f)(self);
        }
    }
}

fn main() {
    let mut bf1 = BoolAndFns1 {
        b: false,
        fs: vec![FnWrapper1 {
            f: |bf| {
                bf.b = true;
            },
        }],
    };
    bf1.meth();

    let mut bf2 = BoolAndFns2 {
        b: false,
        fs: vec![FnWrapper2 {
            f: Box::new(|bf2| {
                bf2.b = true;
            }),
        }],
    };
    bf2.meth();
}

When I try to compile this program, the Rust compiler gives an error for FnWrapper2:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:30:16
   |
29 |         if let Some(wrapper) = self.fs.last_mut() {
   |                                ------- first mutable borrow occurs here
30 |             (wrapper.f)(self);
   |             ----------- ^^^^ second mutable borrow occurs here
   |             |
   |             first borrow later used by call

However, there is no error for FnWrapper1.

As such, I have two questions:

  1. Why is it valid to mutate a variable a second time when passing a mutable reference to it into a function pointer, but not when passing it into a boxed trait object?
  2. Is it possible to make the second version work?

Solution

  • The difference is, that function pointers do implement Copy so wrapper.f isn't borrowing from self. You can avoid aliasing by temporarily removing the function from the vector instead of referencing it:

    impl BoolAndFns2 {
        pub fn meth(&mut self) {
            if let Some(wrapper) = self.fs.pop() {
                (wrapper.f)(self);
                self.fs.push(wrapper);
            }
        }
    }
    

    If you don't want to or can't remove the pointer, you'll have to find a way to share the trait objects, without borrowing, for example by sharing ownership with Rc:

    use std::rc::Rc;
    pub struct FnWrapper3 {
        pub f: Rc<dyn Fn(&mut BoolAndFns3)>,
    }
    
    pub struct BoolAndFns3 {
        pub b: bool,
        pub fs: Vec<FnWrapper3>,
    }
    
    impl BoolAndFns3 {
        pub fn meth(&mut self) {
            if let Some(wrapper) = self.fs.last() {
                let f = Rc::clone(&wrapper.f);
                (f)(self);
            }
        }
    }