Can every `FnMut` be converted to an `Fn`? Is this about re-entrancy?

When thinking about whether to use Fn or FnMut for this bound on F, I wondered if every FnMut can be converted to an Fn where needed (assuming we're single-threaded).

Consider these two conversion functions which convert between Fn and FnMut:

pub fn fn_to_fn_mut<F, A, B>(f: F) -> impl FnMut(A) -> B
where
    F: Fn(A) -> B,
{
    // works because `FnMut` is a supertrait of `Fn`
    f
}

pub fn fn_mut_to_fn<F, A, B>(f: F) -> impl Fn(A) -> B
where
    F: FnMut(A) -> B,
{
    let f = std::cell::RefCell::new(f);
    move |a| (f.borrow_mut())(a) // how to trigger this to panic?
}

Example use:

fn twice<F, A, B>(f: F, arg: A) -> B
where
    F: Fn(A) -> B,
    A: Clone,
{
    f(arg.clone());
    f(arg)
}

fn main() {
    let mut counter: i32 = 0;
    let f = |()| counter += 1;
    twice(fn_mut_to_fn(f), ());
    assert_eq!(counter, 2);
}

(Playground)

I tried to create an example where the following line actually panics:

    move |a| (f.borrow_mut())(a) // how to trigger this to panic?

I feel like it has to do with re-entrancy, but I'm still unsure about it. Can I make it to panic at all?

It's not directly pointed at you because your code is sound, but every time I hear people say "assuming we're single-threaded" to justify shared mutation it reminds me of this blog post:

You are right in that the only way to make this code panic is if we do f.borrow_mut() while f is already being called. For a "trivial" function it's going to be pretty hard to pull this off, but I could see it happening in really complex scenarios, expecially when Arc<dyn Trait> might be involved and the closure is (indirectly) passed into itself.

I'm not sure if it counts because the fault isn't with fn_mut_to_fn(), but this will panic:

use std::cell::RefCell;

pub fn fn_mut_to_fn<F, A, B>(f: F) -> impl Fn(A) -> B
where
    F: FnMut(A) -> B,
{
    let f = RefCell::new(f);
    move |a| (f.borrow_mut())(a)
}

fn main() {
    let f = RefCell::new(|x: i32| println!("{}", x));
    
    let wrapped_fn = fn_mut_to_fn(|x| (f.borrow_mut())(x));
    
    let _borrow = f.borrow(); 

    wrapped_fn(1); 
}

(playground)

Well, I tried similar examples, and they always panicked outside fn_mut_to_fn. So it doesn't count. :wink:

I feel the same.


Not sure if this can work at all. I tried:

fn main() {
    let f = &|x: &dyn Fn(_)| {
        x(x)
    };
    f(f);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:3:11
  |
3 |         x(x)
  |         - ^ cyclic type of infinite size
  |         |
  |         arguments to this function are incorrect
  |
help: use parentheses to call this trait object
  |
3 |         x(x(/* value */))
  |            +++++++++++++

error[E0644]: closure/generator type that references itself
 --> src/main.rs:2:14
  |
2 |     let f = &|x: &dyn Fn(_)| {
  |              ^^^^^^^^^^^^^^^ cyclic type of infinite size
  |
  = note: closures cannot capture themselves or take themselves as argument;
          this error may be the result of a recent compiler bug-fix,
          see issue #46062 <https://github.com/rust-lang/rust/issues/46062>
          for more information
  = note: required for the cast from `[closure@src/main.rs:2:14: 2:29]` to the object type `dyn Fn(_)`

Some errors have detailed explanations: E0308, E0644.
For more information about an error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 2 previous errors

:exploding_head:

I wonder if this makes the following sound:

pub fn fn_mut_to_fn<F, A, B>(f: F) -> impl Fn(A) -> B
where
    F: FnMut(A) -> B,
{
    let f = std::cell::UnsafeCell::new(f);
    // probably unsound, but can someone prove it?
    move |a| (unsafe { &mut *f.get() })(a)
}

(Playground)

I guess there is a way to somehow give the closure access to itself (thus making the above unsafe code unsound), but I don't know how.

Every such example is trivial to exploit using recursion: Playground

let f: Rc<RefCell<Rc<dyn Fn(i32)>>> = Rc::new(RefCell::new(Rc::new(|x: i32| println!("{}", x))));
let clone = f.clone();
let g = Rc::new(fn_mut_to_fn(move |x| clone.borrow()(1)));
*f.borrow_mut() = g.clone();
g(1);

This panics with already borrowed: BorrowMutError if and only if you actually call g.

4 Likes

:+1:

I wouldn't call this "trivial" though. :sweat_smile:

Well the Rc gymnastics is only needed to avoid lifetime errors; the rest is just the old straightforward "initialize to something irrelevant then replace when self-reference is already in place" trick.

1 Like

My slightly modified Playground with your unsafe FnMut -> Fn conversion exhibits explicit UB under Miri.

7 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.