Skip to content
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

Allow `#[must_use]` on functions, rather than just types. Mark `Result::{ok,err}` `#[must_use]`. #886

Closed
wants to merge 9 commits into from

Conversation

Projects
@huonw
Copy link
Member

huonw commented Feb 19, 2015

Rendered.


This adds a little more complexity to the `#[must_use]` system.

The rule stated doesn't cover every instance were a `#[must_use]`

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

s/were/where

function is ignored (it does cover all instances of a `#[must_use]`
type), e.g. `(foo());` and `{ ...; foo() };` will not be picked up
(that is, passing the result through another piece of syntax). This
could be tweaked.

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

This paragraph is kinda hard to parse (two asides in the same "sentence")

This comment has been minimized.

@huonw

huonw Feb 19, 2015

Author Member

Adjusted.

code-bases: 2 instances in the rust-lang/rust codebase (vs. nearly 400
text matches for `let _ =`) and 4 in the servo/servo (vs. 55 `let _
=`). Yet another way to write this is `drop(foo())`, although neither
this nor `let _ =` have the method chaining style.

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

Is it within scope for this RFC to try to settle on a convention?


- Provide an additional method on `Result`, e.g. `fn ignore(self) {}`, so
that users who wish to ignore `Result`s can do so in the method
chaining style: `foo().ignore();`.

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

👍 from me. Hooray for semantics (and easy to grep/lint for once you're ready for production!)

This comment has been minimized.

@reem

reem Feb 19, 2015

Agreed. I like this solution, though am also in favor of the RFC as a generally useful construct.

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

Although ignore as a method is a bit wanting if any return type can be marked as must_use. Still need a general convention (unless Result is the only one that "needs" ignore-ability).

This comment has been minimized.

@reem

reem Mar 6, 2015

We could add an ignore method to all types (with a blanket impl) that's just a method form of drop.


- Adjust the rule to propagate `#[must_used]`ness through parentheses
and blocks, so that `(foo());`, `{ foo() };` and even `if cond {
foo() } else { 0 };` are linted.

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

Any idea how complicated this would be to do?

This comment has been minimized.

@eddyb

eddyb Feb 19, 2015

Member

I think the easiest thing to do would be to track whether the result of evaluating the current expression is obviously unused - that would propagate down through (foo()), { foo() }, if/else, match, etc.

This comment has been minimized.

@oli-obk

oli-obk Feb 19, 2015

Contributor

So basically automatic derivation of must_use for all types (if you have a sub-element that has must_use, you are must_use)? any expression doing a no-op on the object will also return a must_use object -> error message is forwarded outside the expression.

This comment has been minimized.

@Manishearth

Manishearth Mar 6, 2015

Member

@oli-obk it doesn't look like this applies to types.

# Unresolved questions

- Are there many other functions in the standard library/compiler
would benefit from `#[must_use]`?

This comment has been minimized.

@Gankro

Gankro Feb 19, 2015

Contributor

Its been suggested elsewhere that the comparison operators should be linted thusly. No reasonable code should have x == y; as a standalone expression.

Would it be possible to mark a trait's method as must_use, affecting all implementers?

call is intentional or just an accidental oversight.

Rust has got a lot of mileage out connecting the `#[must_use]` lint to
specific types: types like `Result`, `MutexGuard` (any guard, ina

This comment has been minimized.

@reem

reem Feb 19, 2015

typo at ina

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

-1. I, and most code-bases, use ok() to ignore Result.

@huonw

This comment has been minimized.

Copy link
Member Author

huonw commented Feb 19, 2015

As stated in the RFC, there's not a huge amount of evidence for "most".

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

You know what they say: Absence of evidence is not evidence of absence.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Feb 19, 2015

👍 Seeing .ok() anywhere is still quite confusing for me.
.ok().unwrap() makes me wonder what is wrong with their error type that results in .unwrap() not being used.
And foo().ok(); was "obviously" meant to be used and the author must have forgotten to do something with it.
Very rarely have I seen an use I consider legitinately, and even then a match or if-let would be clearer.
@nikomatsakis I know you've been using Result in rustc algorithms a lot lately, what are your thoughts on the matter?

@huonw

This comment has been minimized.

Copy link
Member Author

huonw commented Feb 19, 2015

@mahkoh I have looked, I have found evidence of absence. If you can find a wealth of libraries from a variety of authors that use .ok() for ignoring Results the position of the RFC can be reconsidered, but, at the moment, using .ok(); for this purpose seems to be quite unidiomatic (or, at least, uncommon).

Even then, I would prefer having an explicit .ignore() method than abusing .ok() for this purpose, since the former clearly states what it is for while the latter has the problems described in the motivation section.

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

I have looked, I have found evidence of absence.

That's great! I'm looking forward to your analysis of most code-bases on crates.io. Btw: Looking at two trees and not finding any squirrels does not extrapolate to "there are no squirrels in the forrest."

If you can find

Let's not try to move the burden of proof. You're the one proposing a change and making claims about "ok" not being used for this purpose in most code-bases.

Even then, I would prefer having an explicit .ignore()

Then we agree. Unfortunately there doesn't seem to be a consensus for changing ok or adding ignore.

@huonw

This comment has been minimized.

Copy link
Member Author

huonw commented Feb 19, 2015

$ ack-grep --type rust 'let _ =' ~/.cargo ~/.multirust/toolchains/*/cargo | wc -l
533
$ ack-grep --type rust '\.ok\(\);' ~/.cargo ~/.multirust/toolchains/*/cargo | grep -v '=.*ok' | wc -l
64
@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

Is that your detailed analysis of most code-bases on crates.io?

@huonw

This comment has been minimized.

Copy link
Member Author

huonw commented Feb 19, 2015

Clearly not, but the claim "most code-bases use ok() to ignore Result." is looking pretty unlikely.

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

~/rust$ grep "let _ =" $(find -name "*.rs") | wc -l 
17
~/rust$ grep "\.ok()" $(find -name "*.rs") | wc -l 
30

Please focus on proving your claims for now. In case you have forgotten:

Result::ok is occasionally used for silencing the #[must_use]
error of Result, i.e. the ignoring of foo().ok(); is
intentional. However, the most common way do ignore such things is
with let _ =, and ok() is rarely used in comparison, in most
code-bases

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

If the first part of this RFC (#[must_use] on functions) is implemented, then #[must_use] should be removed from Result.

@oli-obk

This comment has been minimized.

Copy link
Contributor

oli-obk commented Feb 19, 2015

I think it's irrelevant whether ok() is used often or not. It has a purpose: converting a Result into an Option. let _ = has no other purpose than ignoring results. A rule of thumb in safety is to keep dangerous and safe actions as far away from each other as possible. Exaggerated: "Don't put the nuclear bomb launch button next to the coffee button".

@oli-obk

This comment has been minimized.

Copy link
Contributor

oli-obk commented Feb 19, 2015

If the first part of this RFC (#[must_use] on functions) is implemented, then #[must_use] should be removed from Result.

That would require every function that returns a Result to be annotated. Some might be missed. The Result type exists explicitly for two things: error reporting and ensuring errors don't get lost.

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Feb 19, 2015

That would require every function that returns a Result to be annotated

That's obviously not true. It only requires functions where the return value must be used to be annotated. E.g. if a function is changed from

fn f() -> Option<T>;

to

fn f() -> Result<T, ()>;

then that does not imply that ignoring the return value suddenly becomes more or less dangerous.

@huonw

This comment has been minimized.

Copy link
Member Author

huonw commented Feb 19, 2015

~/rust$ grep "let _ =" $(find -name "*.rs") | wc -l

AFAIK, that is the code of a single author who is clearly very attached to using ok() for this purpose (why else would you be fighting so hard?). Further, ok() has actual non-ignoring uses (when someone wants and uses the returned Option), which your grep does not distinguish. I've been careful to use .ok(); (with the semicolon) to try to focus on the interesting cases.

If "most" code bases use it for this purpose, it shouldn't be too hard for you to find a few on github to point me to.

I did my own random sample of 50 projects on Rust CI, and added a write up, with statistical analysis, to the end of the RFC. I'm now convinced that this will have minimal impact on most code. Unless I see some very strong evidence to the contrary, I'm considering the objection that ok is commonly used for this purpose just outright wrong.

If the first part of this RFC (#[must_use] on functions) is implemented, then #[must_use] should be removed from Result.

I'm slow, and your customarily terse comments are not at all self-explanatory. Please expand.

@fenhl

This comment has been minimized.

Copy link

fenhl commented Dec 14, 2016

See #1812 for some motivation.

@Philipp91

This comment has been minimized.

Copy link

Philipp91 commented Dec 15, 2016

More motivation: In an FFI, you get lots of c_ints as return values. As far as I can tell, you're in no position to change that (make it a Result<c_int, ()> or something like that) and you cannot flag c_int as #[must_use] in general. Still some of those return values are safe to ignore whereas others aren't. And in some cases (particularly when the FFI is generated from C code that contains some __attribute__ ((warn_unused_result)) attributes) the information is easily available. Allowing #[must_use] would make this information usable and useful.

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Jan 11, 2017

This came up again in the context of #1812, which led to a proposal to close #1812 and re-open this. Anyone interested in working on a revived RFC?

I would suggest focusing first on support for #[must_use] on functions, without specifying any particular functions to mark with that attribute. We can then handle adding that attribute to specific functions afterward, likely with associated crater runs to determine the impact of doing so.

@withoutboats withoutboats reopened this Jan 11, 2017

@withoutboats withoutboats self-assigned this Jan 11, 2017

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Jan 11, 2017

We have found a motivation for this in #1812 - PartialEq::partial_eq. We'd have to check the impact, but it seems clear that foo == bar; is the result of either some confusion on the author's part or a mistyped assignment.

@ssokolow

This comment has been minimized.

Copy link

ssokolow commented Jan 11, 2017

@withoutboats Wow... if that doesn't have prohibitive impacts, it'd feel like a direct act of one-upmanship to Python's decision to sacrifice if let's ancestors (eg. if (entry = thing.pull()) {) on the altar of typo-prevention... and that's a good thing.

(And if it is blocked by the stability promise, I think it's good reason to start explicitly maintaining a list of "we wish we'd thought of this sooner" features that people writing "Rust-inspired languages" (ie. Rust 2.0 without the Perl6/Py3k/etc. drama) should consider.)

Also, I think the point made by @Philipp91 deserves more attention.

Even without automatic translation of __attribute__ ((warn_unused_result)), #[must_use] on functions feels like a great way to counterbalance the effects of C's primitive type system on -sys crate internals.

With automatic translation, it would help binding generators to have that "go above and beyond" feel that already draws praise in areas like rustc's error messages.

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Jan 11, 2017

@ssokolow since a) its a warn and not an error, b) you can fix it with let _ = foo == bar;, c) why would anyone ever write foo == bar;?, I'm optimistic that the impact will be small and acceptable.

We'd need an implementation and a crater run. I agree with @joshtriplett that we should accept & implement this without specifying which functions to apply it to, and then just do crater runs on applying it things (IMO without RFCs but YMMV).

@jmacdonald

This comment has been minimized.

Copy link

jmacdonald commented Jan 28, 2017

I'd love to see this, especially as a means to make a new ignore method on Result the idiomatic way to suppress these warnings. The syntax is much more intention revealing than ok() and let _ = ...;, and feels like a great fit, especially for Rust applications, where typical error propagation practices may not be the best fit (and the very existence of these other idioms seems to hint at a need for this sort of syntax).

@Thiez

This comment has been minimized.

Copy link

Thiez commented Jan 29, 2017

So you would especially like to have this feature so that a new method could be introduced specifically to avoid it? That doesn't sound very convincing to me 😛

What exactly would make foo().ignore(); more intention-revealing that let _ = foo();? At first sight it looks more obvious, but let _ = ... means "ignore" everywhere, while .ignore() would mean ignore only on Result. So it seems to me that the let _ = ... form is actually more intention revealing, because it means the same thing in all possible contexts.

@jmacdonald

This comment has been minimized.

Copy link

jmacdonald commented Jan 29, 2017

No, the benefit of this feature is discouraging the use of ok()/err() to suppress must-use warnings (or warning about it as an unintended side-effect of their usage). 😄

As for the let _ = ... idiom, I'd argue that it's just the underscore that means ignore, and that its use is best suited to individual positional arguments, tuple element destructuring, or match branches that you'd want to ignore (where a placeholder must exist). There's already an idiom for discarding a return value from an expression: the semi-colon! Introducing let to bind the result to a discarded variable feels odd, at least to me. If that's the idiom we want to encourage, it'd be great to add a reference to it in the Error Handling doc section, or in the upcoming book.

Great point about the ignore() solution only applying to Result, though; you'd need something that applies to any type or function that could raise this warning.

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Feb 4, 2017

I'm intrigued by the idea of ignore() instead of let _ = , because I think its much clearer, but it seems like its an idea to consider separately from this RFC.

This RFC needs some edits to get it into the current idea behind this proposal:

  • Remove the references to Result::ok and Result::err (except maybe as a possible motivation)
  • Add the partial_eq example to the motivation.

However, @huonw is no longer a contributor, even if someone else makes the edits I don't think we can get them onto this branch without his merging them. I don't know what the procedure here is, we may need to open a new PR after all.

If anyone wants to volunteer to make the edits (possibly @crumblingstatue ?) we could probably get this into FCP soon; based on the discussion here and on #1812, this seems pretty well-discussed & without outstanding issues.

@Wyverald

This comment has been minimized.

Copy link

Wyverald commented Feb 4, 2017

There's already an idiom for discarding a return value from an expression: the semi-colon!

Isn't that exactly the source of mistakes, though? (because the semi-colon is easily used to discard return values that we normally wouldn't want to discard)

A more radical way to look at this, IMO, would be to enforce that the expression preceding ; has to have type (). This essentially means that nothing can be implicitly discarded by chaining ;.

@arielb1

This comment has been minimized.

Copy link
Contributor

arielb1 commented Feb 6, 2017

A more radical way to look at this, IMO, would be to enforce that the expression preceding ; has to have type (). This essentially means that nothing can be implicitly discarded by chaining ;.

There are several functions that return an "optional" result (IIRC some versions of Vec::push used to return a reference to the new element), and this would make using them hard.

@Wyverald

This comment has been minimized.

Copy link

Wyverald commented Feb 7, 2017

Indeed, the "no implicit discard" rule wouldn't work very well with this kind of APIs, but maybe those APIs are the exception instead of the norm, and we should be annotating them with #[can_ignore] or something.

Just to clarify, I'm not pushing for the adoption of this rule -- it would be such a breaking change, after all. But I still think we might be overestimating how often return values need to be discarded implicitly, and if we were to design everything differently, maybe this rule could've been considered.

@iopq

This comment has been minimized.

Copy link
Contributor

iopq commented Feb 7, 2017

I also came here from #1812 - it's great motivation. I just saw a bug in Android source code in the form of modem_reset_flag == 0;

I realized Rust does NOT do better in this case

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Feb 8, 2017

I'd like to avoid derailing this RFC with even stricter proposals. I think this RFC makes sense as a concrete step that would have minimal impact or invasiveness for existing code and projects.

Please, by all means, go ahead and propose a separate RFC (or perhaps a pre-RFC first) on a more universal "never ignore return values" mechanism; at a minimum, that might make sense as an off-by-default lint, even if not an on-by-default one.

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Feb 8, 2017

As I said before, I'd like to fcp this rfc as soon as it's been amended to reflect the current status. If anyone can write up an amendment it would be great.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 11, 2017

Historical note:

A more radical way to look at this, IMO, would be to enforce that the expression preceding ; has to have type (). This essentially means that nothing can be implicitly discarded by chaining ;.

Many of us have been in favor of this in the past, but (imo) we definitively decided against it when we allowed APIs like insert() or push(). The notion of #[must_use] types was intended as the compromise. I do not think we should relitigate that decision but rather stay within the spirit (as this RFC indeed proposes to do) of generally permitting unused values but allowing types/functions to declare when we should warn.

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Feb 28, 2017

Is anyone interested in updating this RFC to reflect the learnings from #1812 ? @crumblingstatue ? @joshtriplett ?

@carols10cents carols10cents added this to Merge proposed in Tracker Apr 26, 2017

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Apr 29, 2017

Just to make things simpler I'm closing this and making #1940 the PR for tracking this RFC.

aturon added a commit that referenced this pull request Jul 17, 2017

Merge pull request #1940 from iopq/master
Updated RFC #886 with lessons learned from #1812
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.