-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Deny float matches #84045
Deny float matches #84045
Conversation
(rust-highfive has picked a reviewer for you, use r? to override) |
5df6a03
to
31b6dab
Compare
This comment has been minimized.
This comment has been minimized.
31b6dab
to
38cb1fe
Compare
Nominating for T-lang discussion. I think we may want a crater run to assess to the scope of the breakage here; re-reading the tracking issue #41620 does not suggest to me that we have a particular consensus on this lint. Some questions I have:
|
Allow me to be more precise: Most of the problem is a literal is a literal, and therefore can be interpreted as either an f32 or an f64. Consider the following code: let x = 1.0000001;
assert!(match x {
1.00000011 => true,
_ => false,
}); As is, x is by default implicitly an f64, so the other literal is also read as an f64. This assert fails. let x: f32 = 1.0000001; The other literal is also read as an f32, and the assert succeeds. Now, I was able to get this working, of course: let z = 1.00000011;
let x: f32 = 1.0000001;
assert!(z == x); But by contrast, however, I tried to get this working with, say, matching on a const value, or using a const comparison, since match is by all rights against a const value in a pattern and therefore should be compared against that, and what I found was that I kept running into errors like this: error[E0308]: mismatched types
--> src/main.rs:5:9
|
2 | const Z: f64 = 1.00000011;
| -------------------------- constant defined here
3 | let x: f32 = 1.0000001;
4 | let y = match x {
| - this expression has type `f32`
5 | Z => true,
| ^
| |
| expected `f32`, found `f64` or this: error[E0308]: mismatched types
--> src/main.rs:5:9
|
4 | assert!(match x {
| - this expression has type `f32`
5 | const { 1.00000011 } => true,
| ^^^^^^^^^^^^^^^^^^^^ expected `f32`, found `f64` Type errors! Type errors everywhere! There's an obvious (cursed) golden path to successfully misusing a literal here, but it's the kind of thing that is easy to lint against and should instinctively make one's eyebrow raise. Even a slight deviation from that golden path slams the programmer against a wall of problems, which is just enough friction, I would argue, to make it much harder to accidentally introduce this kind of error while refactoring, and much less of a concern in any other context. As for matches on ranges using float literals, they are included in the same lint, so would also be denied. My feeling is that comparing against values differing by some epsilon is worthwhile but that people should ideally not be working with literal ranges if the ranges are themselves so... fuzzily bounded. Also, the entire problem with "consts are not functionally equivalent to literals" again rears its head here, when logically they arguably are supposed to be equivalent in patterns, as has been argued before! |
Please don't merge this change! I've looked through the relevant threads and feel that as long as a compiler warning is thrown with a link to this issue it should be enough for developers to see and acknowledge the risks. It seems from feedback on this issue that a fair amount of people think the same way as I do and want to keep things the way they are. Can this please remain as a compiler warning? |
Keeping it as a warning does not mean it requires an acknowledgement. As-is, someone can ignore the warning outright, never reading it, and many developers do use workflows that abstract away the actual compiler output, such as in IDEs. Likewise, upgrading something to deny-by-default does not prevent allowing the lint. By requiring an explicit allowing of the lint instead, a programmer who wishes to use this will be forced to consider whether they truly do understand the risks involved and issue a programmatic acknowledgement. This is the standard the Rust project actually uses when it wants someone to opt-in to something considered a risky interface, e.g. nightly features. This would not be the case if it was a hard error. A hard error would actually prevent usage. It has been proposed to make this a hard error, but I am not entirely sure I agree with that. I am, however, absolutely confident in upgrading this to deny-by-default. This is because in order to make floating point literals in patterns make sense, I believe it would require an extensive proposal reconciling them in relation to other evolving features of the Rust language, including I genuinely believe that, until such an event has occurred, they should be treated with at least as much forewarning as any other unstable interface in the Rust compiler. |
We discussed this in our @rust-lang/lang meeting on 2021-05-04. The plan was for me to try and write-up a proposal of next steps and float it by the const eval or const generics group. I have to say that my current inclination is that we should effectively adopt the current behavior around floating point literals (which I believe is that, for example, I don't think there is any real blocker here. I believe it's forwards compatible with all variants we might use for matching on constants, since floats are a "leaf value" (the disagreement that we had was how to manage matching on constants of aggregate type: should we try to use However we land on this, I do want to thank @workingjubilee for binding up the issue. |
cc @rust-lang/wg-const-eval and @rust-lang/project-const-generics on this comment |
as long as that is true (float matching thus essentially being equal to invoking PartialEq), I am in favor of just keeping the current behaviour, and doing as @workingjubilee suggests: deny lint, never hard error, possibly no lint in the future. |
I'm with you right up until deny lint =) I have not yet fully reconciled myself to even the idea of a deny lint, I think, but the idea of having a deny lint that we might possibly remove in the future seems odd. The main reason to have deny lints that I have heard is basically "something that is almost certainly a bug and so obscure that you would want an allow anyway". I guess matching on floats outside of a range might fall into that category -- the other I've hard that seems convincing is overflowing literals like 296_u8. |
I do not believe floating point range patterns should be treated specially compared to "singleton" literals because let x: f32 = 1.0000001;
assert!(match x {
1.00000011..=2.0 => true,
_ => false,
}); still flies. |
IMO, This has some direct consequences on their use in patterns:
I think this corresponds to the current implementation, but it might be good to check. (This will be easier to do/see when @oli-obk finishes porting the pattern compilation to valtrees.) On top of all that, I think the question should be whether we want to allow any non- But I don't think it makes sense to discuss this in isolation, this should be seen in the greater context of the
That is only possible for |
Yes, sounds good. Some random links related to the subject:
Here's how to pattern-match on a non- |
Dropping nomination in favor of the (already proposed, though not currently scheduled) lang design meeting. |
☔ The latest upstream changes (presumably #86888) made this pull request unmergeable. Please resolve the merge conflicts. |
@workingjubilee @rustbot label: +S-inactive |
This upgrades the "warn on float literals in patterns" lint tracked in #41620 to "deny". It's been quite enough time since rustc started issuing the warning.
I believe this probably should be run against crater first. Though it's not like it hasn't been an entire edition since the warning went up.