Skip to content

rustc error msg poor when *indirectly* expecting F: Fn but given F: FnOnce #32799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pnkfelix opened this issue Apr 7, 2016 · 2 comments
Closed

Comments

@pnkfelix
Copy link
Member

pnkfelix commented Apr 7, 2016

The rustc error message is mystifying to users when they unknowingly have created a closure that only implemented FnOnce and are passing it into a context that indirectly requires an Fn.

(By indirectly, I mainly mean a case like where one has a trait Handler, an impl<F:Fn> Handler for F { ... }, and the context is a function argument of type H where <H: Handler>)

A concrete example:

pub struct Response;
pub struct Router;
pub struct IronError;
pub struct Request;

pub type IronResult<T> = Result<T, IronError>;

pub trait Handler { }

impl<F> Handler for F
    where F: Send + Sync + Fn(&mut Request) -> IronResult<Response>
{ }

impl Router {
    pub fn post_h<H>(&mut self, _handler: H) -> &mut Router
        where H: Handler
    { unimplemented!() }

    pub fn post_f<H>(&mut self, _handler: H) -> &mut Router
        where H: Send + Sync + Fn(&mut Request) -> IronResult<Response>
    { unimplemented!() }
}

fn body(_s: String) -> IronResult<Response> { unimplemented!() }

fn main() { attempt(Router); }

#[cfg(not(this_works))]
fn attempt(mut r: Router) {
    let owned = String::new();
    r.post_h(move |req: &mut Request| -> IronResult<Response> { body(owned) });
}

#[cfg(this_works)]
fn attempt(mut r: Router) {
    let owned = String::new();
    r.post_h(move |_eq: &mut Request| -> IronResult<Response> { body(owned.clone()) });
}

#[cfg(this_fails_to_compile_but_has_a_good_error_message)]
fn attempt(mut r: Router) {
    let owned = String::new();
    r.post_f(move |_eq: &mut Request| -> IronResult<Response> { body(owned) });
}

Trying to compile the above (http://is.gd/qBje7g) yields the following error message:

<anon>:31:7: 31:13 error: the trait `for<'r> std::ops::Fn<(&'r mut Request,)>` is not implemented for the type `[closure@<anon>:31:14: 31:78 owned:std::string::String]` [E0277]
<anon>:31     r.post_h(move |req: &mut Request| -> IronResult<Response> { body(owned) });
                ^~~~~~
<anon>:31:7: 31:13 help: see the detailed explanation for E0277
error: aborting due to previous error

This error message is accurate, but not specific enough. In particular, I find that people often focus on the lifetime(s) reported in the message and are wondering if that is somehow the reason that their closure is not implementing the listed trait bound.

I have found that only after taking steps such as wrapping the closure expression in a call to a function of the form

fn id<F>(x: F) -> F where F: Fn(&mut Request) -> IronResult<Response> { x }

only then do I get an error message that points out that my closure doesn't implement Fn at all, and that I need to avoid moving the captured owned state out the closure when it is executed.


It is worth noting that the error message you get when the F: Fn requirement is more directly imposed on the closure; in that situation, you get something like:

<anon>:44:70: 44:75 error: cannot move out of captured outer variable in an `Fn` closure [E0507]
<anon>:44     r.post_f(move |_eq: &mut Request| -> IronResult<Response> { body(owned) });
                                                                               ^~~~~
<anon>:44:70: 44:75 help: see the detailed explanation for E0507
error: aborting due to previous error

which users tend to be able to deal with. (Basically this is just me repeating the point that adding a call to a id function with the specific expected bound can help yield a better error message.)

@pnkfelix
Copy link
Member Author

pnkfelix commented Apr 7, 2016

By the way, @nikomatsakis tells me that #32780 might resolve this issue.

@Mark-Simulacrum
Copy link
Member

No, Github, I didn't want to post...

Anyway, this appears to be fixed today, though the error message could be slightly better.

rustc 1.19.0-nightly (777ee2079 2017-05-01)
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> <anon>:31:14
   |
31 |     r.post_h(move |req: &mut Request| -> IronResult<Response> { body(owned) });
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: the requirement to implement `Fn` derives from here
  --> <anon>:31:7
   |
31 |     r.post_h(move |req: &mut Request| -> IronResult<Response> { body(owned) });
   |       ^^^^^^

error: aborting due to previous error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants