Skip to content

Misleading error message talks about return value of closure when the issue is about Fn vs FnOnce #70879

@Darksonn

Description

@Darksonn

In this code:

pub struct Shared {}

pub trait State {
    fn new(shared: Rc<RefCell<Shared>>) -> Self where Self: Sized;
}

pub struct StateManager {
    factories: HashMap<String, Box<dyn Fn() -> Box<dyn State>>>,
    shared: Rc<RefCell<Shared>>
}

impl StateManager {
    pub fn new() -> Self {
        Self {
            factories: HashMap::new(),
            shared: Rc::new(RefCell::new(Shared {}))
        }
    }
    pub fn register_state<S: State + 'static>(&mut self, name: String) {
        let shared = self.shared.clone();
        self.factories.insert(name, Box::new(move || {
            // uncomment this to make it compile:
            // let shared = shared.clone();
            Box::new(S::new(shared)) as Box<dyn State>
        }) as Box<dyn Fn() -> Box<dyn State>>);
    }
}

playground

I expected to see this happen: An error that explains that the closure is FnOnce due to giving shared away.

Instead, this happened: An error that says the closure is not dyn Fn(), which appears to be talking about its return value. Note in particular the help note about wrapping it in a closure with no arguments.

The error is:

error[E0277]: expected a `std::ops::Fn<()>` closure, found `[closure@src/lib.rs:25:46: 29:10 shared:std::rc::Rc<std::cell::RefCell<Shared>>]`
  --> src/lib.rs:25:37
   |
25 |           self.factories.insert(name, Box::new(move || {
   |  _____________________________________^
26 | |             // uncomment this to make it compile:
27 | |             // let shared = shared.clone();
28 | |             Box::new(S::new(shared)) as Box<dyn State>
29 | |         }) as Box<dyn Fn() -> Box<dyn State>>);
   | |__________^ expected an `Fn<()>` closure, found `[closure@src/lib.rs:25:46: 29:10 shared:std::rc::Rc<std::cell::RefCell<Shared>>]`
   |
   = help: the trait `std::ops::Fn<()>` is not implemented for `[closure@src/lib.rs:25:46: 29:10 shared:std::rc::Rc<std::cell::RefCell<Shared>>]`
   = note: wrap the `[closure@src/lib.rs:25:46: 29:10 shared:std::rc::Rc<std::cell::RefCell<Shared>>]` in a closure with no arguments: `|| { /* code */ }
   = note: required for the cast to the object type `dyn std::ops::Fn() -> std::boxed::Box<dyn State>`

By moving things around a little bit, you get the expected error message:

pub fn register_state<S: State + 'static>(&mut self, name: String) {
    let shared = self.shared.clone();
    
    let closure: Box<dyn Fn() -> Box<dyn State>> = Box::new(move || {
        // uncomment this to make it compile:   
        // let shared = shared.clone();
        Box::new(S::new(shared)) as Box<dyn State>
    });
    
    self.factories.insert(name, closure);
}

playground

error[E0507]: cannot move out of `shared`, a captured variable in an `Fn` closure
  --> src/lib.rs:29:29
   |
24 |         let shared = self.shared.clone();
   |             ------ captured outer variable
...
29 |             Box::new(S::new(shared)) as Box<dyn State>
   |                             ^^^^^^ move occurs because `shared` has type `std::rc::Rc<std::cell::RefCell<Shared>>`, which does not implement the `Copy` trait

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-closuresArea: Closures (`|…| { … }`)A-diagnosticsArea: Messages for errors, warnings, and lintsA-suggestion-diagnosticsArea: Suggestions generated by the compiler applied by `cargo fix`D-lack-of-suggestionDiagnostics: Adding a (structured) suggestion would increase the quality of the diagnostic.D-terseDiagnostics: An error or lint that doesn't give enough information about the problem at hand.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions