Emit warnings on parameter list in closures after {
Reproducing steps
This code compiles (and, in my opinion, it shouldn't):
let p = Some(45).and_then({|x|
Some(x * 2)
});
While this doesn't:
let p = Some(45).and_then({|x|
println!("doubling {}", x);
Some(x * 2)
})
Error message 1 (using x)
The error message is absolutely unintuitive and won't help anyone:
<anon>:4:14: 4:15 error: unresolved name `x` [E0425]
<anon>:4 Some(x * 2)
^
error: aborting due to previous error
Error message 2 (not using x)
When the last expression does not use a parameter from the list, the generated error message is even more frightening to newcomers (albeit in retrospect, this one makes more sense):
<anon>:2:22: 5:7 error: the trait `core::ops::FnOnce<(_,)>` is not implemented for the type `core::option::Option<_>` [E0277]
<anon>:2 let p = Some(45).and_then({|x|
<anon>:3 println!("doubling {}", x);
<anon>:4 Some(100 * 2)
<anon>:5 });
<anon>:2:22: 5:7 help: see the detailed explanation for E0277
<anon>:2:22: 5:7 error: the trait `core::ops::FnOnce<(_,)>` is not implemented for the type `core::option::Option<_>` [E0277]
<anon>:2 let p = Some(45).and_then({|x|
<anon>:3 println!("doubling {}", x);
<anon>:4 Some(100 * 2)
<anon>:5 });
<anon>:2:22: 5:7 help: see the detailed explanation for E0277
error: aborting due to 2 previous errors
Proposed solution
Emit a warning when encountering this fallible syntax.
Anecdotic background
Having used a lot of Ruby, I had grown way accustomed to the { |x| x * 2 } syntax. Although I had seen the proper Rust syntax a couple of times, it never was emphasized and the significance never stuck. I spent 30+ minutes trying to figure out why a println! statement breaks the build, generating a random error message that has no obvious connections to the actual bug at hand. Only after inquiring the IRC channel did a solution unveil itself. It shouldn't happen.
I don't think we should make custom-tailored error messages for people trying to write code in their favorite language and then compile it with rustc. Just like we don't issue any special warning on assigning result of assignment to a variable, we shouldn't issue a warning when someone tries to write Ruby lambdas in Rust. People should learn that even if something looks similar, it doesn't mean that it works the same as in their old language.
Xirdus at 2015-07-26 15:23:26
@Xirdus nobody said anything about custom tailored messages and the problem is that the code from Ruby does compile in the first place (see the very first code snippet). Changing that, however, would be a breaking change, so instead, I proposed the solution as a warning that actually makes sense! Maybe don't even change the warning, just make a "note: did you mean
|x| { ... }" ? It would help a lot of people and Rubyists are a target group of the Rust language.Danyel at 2015-07-26 15:47:46
This code compiles (and, in my opinion, it shouldn't):
Just to clarify (for you and anyone who may stumble over this syntax in the future and find this issue):
let p = Some(45).and_then({|x| Some(x * 2) });is the same as
let p = Some(45).and_then({ // This is a block with only one expression. // It returns this expression (the closure): |x| { Some(x * 2) } // braces optional });This is totally legitimate and should compile.
Your other example
let p = Some(45).and_then({|x| println!("doubling {}", x); Some(x * 2) })could be written as
let p = Some(45).and_then({ |x| { println!("doubling {}", x) }; // braces optional Some(x * 2) // returns `Some(i32)` })I understand why rustc's error message is confusing. A solution would be to reformat your code shown in the error and annotating the blocks. (This might make stuff more confusing in other cases, though!)
Pascal Hertleif at 2015-07-26 15:53:12
@killercup thanks for the response! So you're saying we need
rustfmt? I'm all for that and if that fixes the issue, I don't see a problem!Danyel at 2015-07-26 16:03:08
@muja you can already use https://github.com/nrc/rustfmt! (That isn't integrated into the compiler, though, so the error snippets won't get pretty by themselves.)
Pascal Hertleif at 2015-07-26 16:06:24
In the first case, there could plausibly be a hint if you try to use a closure parameter in a subsequent statement, such as
<anon>:4:14: 4:15 error: unresolved name `x` [E0425] <anon>:4 Some(x * 2) ^ <anon>:2:33: 2:34 note: nearest candidate defined at 2:33... <anon>:2 let p = Some(45).and_then({|x| ^ <anon>:3:35: 3:36 note: ...but goes out of scope at 3:35 <anon>:3 println!("doubling {}", x); ^ error: aborting due to previous errorFor the second case, plausibly, when the expression for a closure isn't a block, you could have a hint:
<anon>:2:22: 5:7 error: the trait `core::ops::FnOnce<(_,)>` is not implemented for the type `core::option::Option<_>` [E0277] <anon>:2 let p = Some(45).and_then({|x| <anon>:3 println!("doubling {}", x); <anon>:4 Some(100 * 2) <anon>:5 }); <anon>:2:22: 5:7 help: see the detailed explanation for E0277 <anon>:4:9: 4:22 note: the expression at 4:9... <anon>:4 Some(100 * 2) ^~~~~~~~~~~~~ <anon>:2:32: 3:35 note: ...may have been intended to be part of closure expression at 2:32... <anon>:2 let p = Some(45).and_then({|x| <anon>:3 println!("doubling {}", x); <anon>:3:34: 3:35 note: ...but closure expression ends at 3:34 <anon>:3 println!("doubling {}", x); ^ error: aborting due to previous errorSteven Blenkinsop at 2015-07-26 16:34:55
Note that if the unbound variable error hadn't arisen (e.g. because
xwas defined in the outer scope), this code would have not compiled because the block evaluates toSome(x * 2), which does not implementFnOnce(()) -> Option<U>. You just almost definitely never want to pass a block to any parameter that expects a function, so there's that.There are a couple of different ways rustc could generate a more helpful error message here. If an unbound variable is found in an identifier in the same scope as a previous closure statement that uses that identifier, it could point out that the statement is not inside the closure. If a closure is the first statement of a block and not bound with a
letstatement, it could remind you of Rust's closure syntax. Both of these would be reasonable, and helpful to users confused about closure syntax, though I don't know how easy either of them is.Just like we don't issue any special warning on assigning result of assignment to a variable
This is off-topic, but it would be undoubtedly more helpful if a user wrote e.g.
let x = let y = 0i32and then attempted to usexas ani32, the compiler contained a note explaining whyxwas assigned to()and not a value of typei32. It is fundamentally good for the compiler to help correct confusion about the semantics of the language when that confusion leads to errors.srrrse at 2015-07-27 01:20:40
Given #47763, overly verbose, but educative output actually pointing at the problem:
warning: a closure's body is not determined by its enclosing block --> $DIR/block-enclosed-closure.rs:14:33 | 14 | let _p = Some(45).and_then({|x| | ^^^ this closure's body is not determined by its enclosing block | note: this is the closure's block... --> $DIR/block-enclosed-closure.rs:14:33 | 14 | let _p = Some(45).and_then({|x| | _________________________________^ 17 | | println!("hi"); | |______________________^ note: ...while this enclosing block... --> $DIR/block-enclosed-closure.rs:14:32 | 14 | let _p = Some(45).and_then({|x| | ________________________________^ 17 | | println!("hi"); 18 | | Some(x * 2) 19 | | }); | |_____^ note: ...implicitely returns --> $DIR/block-enclosed-closure.rs:18:9 | 18 | Some(x * 2) | ^^^^^^^^^^^ help: you should open the block *after* the closure's argument list | 14 | let _p = Some(45).and_then(|x| { | ^^^^^ error[E0425]: cannot find value `x` in this scope --> $DIR/block-enclosed-closure.rs:18:14 | 18 | Some(x * 2) | ^ not found in this scope error[E0277]: the trait bound `std::option::Option<_>: std::ops::FnOnce<({integer},)>` is not satisfied --> $DIR/block-enclosed-closure.rs:14:23 | 14 | let _p = Some(45).and_then({|x| | ^^^^^^^^ the trait `std::ops::FnOnce<({integer},)>` is not implemented for `std::option::Option<_>` error: aborting due to 2 previous errorsEsteban Kuber at 2018-01-26 01:13:56