Stream: t-compiler/help

Topic: Why Compile impl FnMut for "Non mutable" closure?


Derek (Jan 30 2020 at 06:02, on Zulip):

In following example, because s1 is not mutable, So closure can't mutate it's captured variable s1. how ever, trait bound "where F: FnMut()" satisfied. No error triggered. But obviously closure can't really mutate anything. why compiler have to impl FnMut in this case.
I also checked source code of libcore/ops/function.rs. the relationship of FnOnce, FnMut and Fn are:

pub trait Fn<Args>: FnMut<Args>
pub trait FnMut<Args>: FnOnce<Args>
pub trait FnOnce<Args>

In another word, FnOnce is a supertrait of FnMut, and FnMut is a supertrait of Fn. So in order to implement Fn, we have to implement FnMut.
But seems it doesn't make sense. if I try to s1.push_str(" Rust"); compiler will throw an error, that basically means compiler is smart enough to know if the closure will make any change. if not, why have to implement FnMut. why can't make Fn is supertrait of FnMut.
I must missed something, but couldn't dig out, hope I can get some help here. Thanks!

fn call<F>(mut func: F)
    where F: FnMut()  {
    func();
}

fn main() {
    let s1 = "Hello".to_string();
    let closure = || {
        // This will trigger "cannot borrow as mutable"
        // s1.push_str(" Rust");
        println!("{} Rust", s1);
    };
    call(closure);
}
Areredify (Jan 30 2020 at 12:07, on Zulip):

FnMut does not mean that the closure mutates state, it means that it might mutate state. That, in turn, means that you have to have a mutable receiver in order to call an FnMut. Fn trait means that the it does not mutate state, so if we make Fn a supertrait of FnMut, it would mean that in order to mutate state, it would also need to not mutate state, which does not make sense.

Derek (Jan 30 2020 at 15:46, on Zulip):

@Areredify Thanks for you reply, I really appreciate. If that's the only reason, In my option,
"so if we make Fn a supertrait of FnMut, it would mean that in order to mutate state, it would also need to not mutate state, which does not make sense."
This can be interpreted this way, Fn is read only. FnMut goes futher, it can mutate. as mutate will need read first. this kind like any permission management, readonly < read/write. So following rule would be more clear:

  1. with move key word, compiler implement FnOnce by default.
  2. without mutate ability. compiler go further implement FnOnce and Fn.
  3. with mutate ability, compiler go further implement FnMut.
    thanks
Charles Lew (Jan 31 2020 at 13:15, on Zulip):

https://doc.rust-lang.org/nightly/reference/types/closure.html#call-traits-and-coercions

Charles Lew (Jan 31 2020 at 13:16, on Zulip):

move is about disabling smart inference, it has nothing to do with call traits.

Last update: Feb 25 2020 at 03:50UTC