-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add the ()
→ Result<(), _>
coercion rule, for removing Ok(())
everywhere.
#2120
Conversation
3c93aab
to
9009837
Compare
Thanks for posting this, @kennytm. There do seem to be irreconcilable differences in #2107, and we discussed at RustConf closing that one for now, revisiting it later as I am of course sad to not get something that works I definitely agree that the coercion is better than the magic AST pass. Having it not be something special will let it grow better as coercions in general develop—imagine being able to pass a My favourite part of this RFC is the "this isn't an exception to the transitivity rules, but it won't be transitive in practice because none of the other coercions are relevant" table. Things becoming weird in combination was the thing that scared me off the coercion path in general, and this makes me not worried. I'm intrigued by |
On its own I think I'd be okay with this, and independently of anything else I would definitely favor making |
I really dislike this - it's only fixing a minor weirdness in some functions, rather than the nice general solution presented in #2107 |
This RFC is better in some ways and worse in others. I don't really like the fact its implemented as coercion, rather than maing "END OF BLOCK" mean For the good stuff, I like that its limited to Also I like that the RFC is very surgical. It doesn't elevate the entire Now for the stuff where its worse. Code like this would now compile: fn foo_0() -> Result<(), E> {
println!("Hello!");
()
} and let r: Result<(), E> = (); and fn foo() -> Result<(), E> {
let mut v = Vec::new();
v.push(42i32)
} All three not really nice, because they are unintended consequences that make code less readable and behaviour less understandable. Even less nice, the coercion allows you to attach fn foo() -> Result<(), E> {
let mut v = Vec::new();
v.push(0i32);
v.push(42)?;
v.push(200)?????;
} All of the problems I've outlined above are fixed by the backup RFC with the |
@glaebhoerl In 2015-next ("1.417") / 2018 ("2.0") where Though, the only problem that coercion + throws can cause is double wrapping, which is a type that practically won't appear, so I think it should be fine for them to coexist. fn foo() -> Result<(), E1> throws E2 {
}
// returns Ok(Ok(())) |
@est31 Yes those are known issues. But when I write the alternatives section of the fn foo() -> Result<(), E> {
let mut v = Vec::new();
v.push(42i32) // <-- no ';'
} but this has the same behavior as fn foo() -> Result<(), E> {
let mut v = Vec::new();
v.push(42i32); // <-- with ';'
} which can also be considered as an advantage. There is also some decision problem like, should fn a() -> Result<(), E> {
if cond() { return Err(E::Bad) }
// ^ yes I've seen code omitting the ';'
}
fn b() -> Result<(), E> {
if cond() { self.value += read()? }
} If we include assignments and |
In the first case, its not really needed (wrapping will work any way), and in the second case you can just put a For the first case, it will most likely desugar the implicit else to: fn a() -> Result<(), E> {
if cond() {
return Err(E::Bad)
}
else {} // implicit else desugaring
} Maybe it desugars to In any case, this would desugar with the RFC to: fn a() -> Result<(), E> {
if cond() {
return Err(E::Bad)
} else {
EmptyTailExpr::empty_tail_expr() // empty tail expr desugaring
}
} As of current Rust, any block with return or a call to a function that has For the second case, the expression gets desugared to a call to My more general concern with coercions of any kind is that they are weakening the type system and as such are really sharp tools, which I would like to avoid unless there is no other alternative. |
I'm not worried about these constructs, since they are really trivial to detect in as a lint (e.g. in clippy). |
I am strongly against this RFC. Removing |
We could do this. I don't think we'd actually have the appetite to cause this much churn in user code over a relatively minor issue though. |
I have encountered the opposite. Students have asked me why they get a mismatched type error when they explicitly used a generics noise
and "what function???"
[EDIT] Suggestion for I could get behind not doing what this RFC suggests, if the errors were improved enormously instead. So they need suggestions and other helpful things, instead of just stating what's wrong. The RFC basically just makes the mechanical changes, that you'd to after encountering the error, implicit. |
I've gotten very wary of implicit coercion because of JavaScript (yes, I know that's an extreme). I have always loved the explicitness of Rust, and that's why I have favored the other RFC more. Here is an example of something I'm afraid of let x = {
// Do some stuff
...
if blah {
Ok(())
} else {
Err("oh no");
}
};
if let Ok(_) = x {
println!("this always prints");
} Oops... In this case, the type system actually would give false confidence. Scary. Also, more generally I would like the solution to be specific to exiting a function or block. |
Admittedly, I think this would be caught by an "unused value" lint. But it is still scary. |
Besides the points I raised in #2107 (comment), one thing I dislike about this RFC is that it feels very un-targeted. Making this a coercion means it could fire anywhere, and I don't think we actually want e.g. something like the following to compile: fn foo(_x: Result<(), ...>) {}
fn bar() {
foo(());
} This is so... arbitrary. Why not also coerce The table arguing that transitivity is not a problem also is very discouraging IMHO. Transitivity is not a problem now, but this is fragile and has to be re-evaluated with every new coercion that's added. The fact that we don't want transitivity here shows that coercions are the wrong tool. This is a hack. |
The arguments I've seen against doing this remind me of the issues with |
I would like to posit that while this RFC does improve code writability, it would make error handling even harder to explain to new users coming to Rust. As such, my view very much echoes that of @nox. I am also in agreement with @oli-obk that current error messages regarding error handling are suboptimal. However, I would argue that with such a change proposed by this RFC, there will be a just as significant number of new users confused about why their function works in the first place. Note that implicit coercion as a concept is explained much later in the book compared to error handling. In any case, I would like to see the potential drawbacks noted by @est31, @mark-i-m, and @RalfJung to be added to the RFC. |
| `()` → `impl Try` | `Try` is not implemented for `()` | | ||
|
||
It should be explicitly mentioned that the `()` → `impl Try` rule does not participate in "receiver coercion" (similarly | ||
the closure → `fn` also does not participate), to avoid truly nonsense expressions like: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced that Result::unwrap_err(())
is any less "nonsense" than ().unwrap_err()
.
And ?
desugars to non-method form, so removing receiver coercion doesn't impact that.
Generally I don't like to add special cases to the language. |
As a smaller step, does anyone have an opinion of allowing |
@egilburg I actually thought of something like that too, but it still feels like special magic for one use case. Perhaps we can make it more general by saying "For enum variants with exactly one parameter ONLY: fn foo() -> Result<(), String> {
if error {
Err("Oh no")
} else {
Ok
}
} |
Or we could just change conventions: Use |
@mark-i-m Your example requires less typing, but it seems to conflate option definition with invocation, similar to how in Rust (unlike languages like Ruby) you cannot invoke a zero-argument function without parentheses. My example proposed similar to what were you saying except the parentheses, e.g., " |
@egilburg Yes, I tried to avoid that. Notice, that I said for "enum variants ... ONLY", not function calls. I think something like this would be very bad for function calls because it obscures what that function call is doing. This doesn't seem to be as much of a problem with enums, though, IMHO. Also, note that you can already create enums without parens if they have no args (e.g. |
I think |
I am very against this RFC because, as stated before, it introduces magic sometimes. I agree However, as stated in the API guidlines in the section about Error-Types:
The same is true for return types. Having userdefined functions return |
I think we could still ask the OP to change the RFC and just present a syntactic sugar However, what I don’t like is that we try to patch the language by adding exceptions and syntactic sugar for a few types – this and all similar RFCs. Still, match a {
Foo => do_something("foo"),
Bar => {}
Zoo => ()
} Both the two latest branches yields the same |
Or, maybe even easier: fn ok<E>() -> Result<(), E> {
Ok(())
} And use it with |
@phaazon Given that the reason both this and RFC 2107 are postponed to give way to the impl-Period, I don't think any RFC in this area will be accepted before 2018. (Also I'm strongly against using |
I do abstractly want to not have to write
In a complicated function, that has all the hallmarks of being the kind of one-character bug that takes days of debugging to find: no diagnostic at compile time, silently swallows error condition at runtime, and the first umpteen times you read the problem code you don't notice the semicolon. This could be addressed with lints — in the compiler, not in clippy! — but I would want to see the lints specified in the RFC, with thought given to false positives and negatives. It may be necessary to write a function with signature I think someone said upthread that the |
I don't find that code terrifying - it doesn't even compile:
If you remove the first branch (
Now if you add type annotations (
Do you perhaps have a more convincing example? |
@stjepang I think you misunderstand: that example doesn't compile right now, but it would with the changes proposed in this RFC, and that's what I'm objecting to. (It is quoted verbatim from the RFC, see under "drawbacks.") |
It wouldn't compile with the proposed changes because (1) the compiler couldn't infer the type of |
@stevenblenkinsop I see, but that doesn't give me enough confidence that, with the proposed changes, something like this can never sneak by silently. The RFC author thought that this example would compile under their proposed changes, after all. I'm asking them to make a case, in the text of the RFC, that this kind of typo cannot ever sneak by the compiler with the existing set of lints, or else propose new lints to fill the gaps. |
What is bugging me the most is that it makes the code unclear. Speaking of code I do work on professionally, I have to assume the next person reading the code has no time at hand and since the project already throws some warnings one more will not make a difference. |
@dns2utf8 I'd like to point out that your linked example fires the "unused result which must be used" lint. |
There've been discussions about having unconstrained type parameters in enum variant constructors default to |
Also, that's a fantastically unintuitive way to say "you have an extra semicolon, and are discarding an error"... |
I find that example terrifying too @zackw |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@est31 true and as I stated, while developing code there are usually many warnings floating around. Second I like rust because there are no exceptions for certain types in the language. |
Why don't we just allow the entire function body to be a catch block? fn foo(x: bool) -> Result<(), u32> catch {
if x {
Err(123)?;
}
} Edit: Oh, I just learned that catch statements still require you to use |
@canndrew some discussion of things more along those lines in #2107 (comment) & follow-ups. |
The final comment period is now complete. |
This RFC is being closed as postponed. While the lang team is very interested in pursuing improvements to Thanks @kennytm! |
Alternative to #2107, considering that is heavily downvoted.
I also have a backup RFC for automatic
Ok(())
insertion as an AST pass, but I'm not convinced that adding a coercion rule is actually worse than the AST trickery, so I'm pushing for coercion instead.cc rust-lang/rust-roadmap-2017#17.