Description
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.)