Why fn can borrow mut refenence of self, while closure can't

type PointFunc = fn(path: &mut Path);
type PointClosure = Box<dyn FnMut(&mut Path)>;
enum Point {
    Fn(PointFunc),
    Closure(PointClosure),
}

struct Path {
    point: Vec<Point>,
}
impl Path {
    fn test(&mut self) {
        match &self.point[0] {
            Point::Fn(f) => f(self),      // <-- no error
            Point::Closure(f) => f(self), // <-- error
        }
    }
}

fn main() {}

Here passing mut ref self to stored closure is not allow, while stored fn is ok. Why?

image

Either take a &mut access to the PointClosure, so as to be able to use the FnMut API, or make PointClosures be &-callable by using Fn(...) instead :slightly_smiling_face:

1 Like

If I use a &mut access to the PointClosure, self can't be passed into Closure f. demo

You are trying to create two mutable references there (as compiler quite clearly explains).

That's because your closure tried to keep the cake and eat it, too: you closure now have two different ways to access itself — one via self (it's hidden argument in captures, but nonetheless quite real) and one via it's argument, &mut Path.

That's precisely what Rust rules are supposed to prevent. Maybe you want to pass all other elements of path into closure, or maybe your closure don't need to be FnMut can can be just Fn, but your design, as it's made here doesn't work.

And that's good thing, too, because I have seen all to many cases in other languages where such design was accepted and almost every time this leads to tears because this aliasing issue is not just bad for optimizer, it's hard to reason about it for the human, too.

Then you have to either pick the other solution (change the closure to just Fn) or find some way to split Path is a struct with the Points and another struct that you'll pass to the closure instead of Path.

3 Likes

This wouldn't work either: closure would have both mutable (via &mut Path argument) and immutable (via hidden self) access to itself which means you would still have the ability to change stuff while someone can look on it (and even execute it!).

Function doesn't have any state, closure does. Closures that don't have any state can be passed around as fn, BTW. Thus if you want such closures in Path everything would just work.

But “normal” tracing-GC “architecture” where you pile up random pointers which turn your data into spaghetti doesn't work in Rust: you have to know who is owner of what.

Indeed, if we look at what f is, we have f ~ &mut self.point[0].0 (that last .0 being the deconstruction of the enum variant).

You then have:

f(self)
// is actually
(&mut self.point[0].0)(self)
//    ^^^^             ^^^^

That is, two things wanted to access self, with the latter being &mut and exclusive, and the former, if using FnMut() and thus &mut self[0]..., also wanting to be exclusive, hence the problem.

  • For instance, consider what would happen if somebody were to write:

    let point = Point::Closure(|path| path.test()); // or `|path| match &mut path[0] { ... }`
    let mut path = Path { point: vec![point] };
    path.test();
    

So, indeed, FnMut() is out of the question, and actually, so is Fn(). Since, even if you could have a shared access to the closure inside the path, you'd still be unable to follow up with exclusive access to the path itself.

  • If you had a Fn(&Path), then you could have shared access to the Path and the closure inside it, concurrently / in an overlapping manner, without problems, btw.
    But let's assume you still want that &mut Path.

What you thus need is a way to have access to that closure, but to stop borrowing the path while doing so. In this case, it will require that you own the closure right before calling it / that it not be owned by path / that it "not be part" of path.

  • Using Clone

    To get an owned instance out of a borrow of it, that is, to perform a &T -> T / a &Self -> Self operation, we have the Clone trait (which encompasses Copy).

    use ::std::rc::Rc;
    
    type PointFunc = fn(path: &mut Path);
    type PointClosure = Rc<dyn Fn(&mut Path)>;
    enum Point {
        Fn(PointFunc),
        Closure(PointClosure),
    }
    
    struct Path {
        point: Vec<Point>,
    }
    impl Path {
        fn test(&mut self) {
            match &self.point[0] {
                Point::Fn(f) => f(self),
                Point::Closure(f) => (f.clone())(self),
            }
        }
    }
    

    So, regarding the OP: what is the difference / why can Point::Fn() get away with "no clone", the answer is that "no clone" is actually false in the Point::Fn(f) branch:

    Point::Fn(f) => f(self),
    

    Here we actually have is:

    Point::Fn(f) => (*f)(self)
    

    That is, we are implicitly Copy-ing f, that is, we are "cloning" it much like we have explicitly done in the Closure branch. So the difference you were looking for, here was the Copy property of fn pointers, which allows for implicit copies (conceptually, like clones), which your Closure couldn't do.

    If you replaced that heap-allocated dyn Fn... with a &'static dyn Fn... in the definition of PointClosure, you'd see that it would act like the fn() pointer case (conceptually, there is no much difference between an fn... pointer and a &'static dyn Fn...).

  • Using mem::replace()

    Another approach to this reëntrancy problem you had: the call of f(self) has access to the whole Path, and thus, to the self.point[0] that we try to be holding for the closure to be usable.

    So, one interesting way to solve this problem is to untangle them: we take / replace the closure before calling it, so that it is not part of self and the borrow does not overlap:

    type PointFunc = fn(path: &mut Path);
    type PointClosure = Box<dyn FnMut(&mut Path)>;
    enum Point {
        Fn(PointFunc),
        Closure(PointClosure),
    }
    
    struct Path {
        point: Vec<Point>,
    }
    impl Path {
        fn test(&mut self) {
            match &mut self.point[0] {
                Point::Fn(f) => f(self),
                Point::Closure(f) => {
                    //                                           dummy value
                    // take it                                   vvvvvv
                    let mut f = ::core::mem::replace(f, Box::new(|_| ())); // note: this does not heap-allocate.
                    // make the call with `f` no longer being there
                    f(self);
                    // put it back.
                    self.point[0] = Point::Closure(f);
                },
            }
        }
    }
    
4 Likes

Thanks. I now understand why fn can use self while Fn can't.

I found I actually need a closure(closure) pattern like

type PointClosure = Box<dyn FnMut(&mut Path)>;
struct Path {
    f: PointClosure,
}
impl Path {
    fn test(&mut self) {
        (self.f)(self);
    }
}

(Which is heavily used in my lua code, and now try to port to rust)

The f store a state(for example time or times), keep applying self and modifing state. So both function and argument must not be a copy. Then the clone or mem::replace is not enough. If f is cloned, the state in real f will not really change. If f is replaced, the real f can't be called again during f(self).

There are tens of f. If I store them as associate function, It's strange and hard to read.

My current solution is convert all my closure to fn, and explicatly store captured value in a state of Path.

:ok_hand:

This is a very good solution, if you can indeed go for it: having closure(closure) in Rust will inevitable require & / shared access, and thus, "prevent" mutation unless you use shared mutability (which would run into its own can of worms).

But if you manage to separate some &mut State for the actual calls, doing closure(&mut state, closure) with & access to the closure, or even a copied one (such as in your fn() case), then you'll have a clean (albeit a bit cumbersome) pattern.

Definitely the best solution if you intend to keep closure(closure, …).

1 Like

The key point, in this case, is that the f of the type & mut Box<dyn FnMut(&mut Path)> is mutably borrowed from self: & mut Path, and the calling of f needs the & mut self as the first argument and requires the & mut Path as the second argument, they have the overlapping NLL at the calling point, rust does not permit there are both immutable and mutable borrowings at the same time, let alone there are two mutable borrowings at the same time in this case, which is exactly the reason for the error.

A workaround for this case may be the following if you do not prepare to change the use of Box

type PointFunc = fn(path: &mut Path);
type PointClosure = Box<dyn FnMut(&mut Path)>;
enum Point {
    Fn(PointFunc),
    Closure(Option<PointClosure>),
}

struct Path {
    point: Vec<Point>,
}
impl Path {
    fn test(&mut self) {
        match &mut self.point[0] {
            Point::Fn(f) => f(self), // <-- no error
            Point::Closure(f) => {
              let c = f.take();  // make the use of the `FnMut` that is irrelevant from `self:Path`
               match c {
                    Some(mut fmut) => {
			            fmut(self);  // now `fmut` is irrelevant from `self:Path`
			            self.point[0] = Point::Closure(Some(fmut));  // give it back to Self:Path
		            }
                    None =>{},
                }
            } // ok
        }
    }
}