Skip to content

Commit f31e5e5

Browse files
authored
Merge pull request #2 from huonw/must-use
Must use
2 parents 7c02100 + ca877fb commit f31e5e5

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed

text/0000-must-use-functions.md

+272
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
- Feature Name: none?
2+
- Start Date: 2015-02-18
3+
- RFC PR: [rust-lang/rfcs#886](https://github.com/rust-lang/rfcs/pull/886)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
8+
Support the `#[must_use]` attribute on arbitrary functions, to make
9+
the compiler lint when a call to such a function is ignored. Mark
10+
`Result::{ok, err}` `#[must_use]`.
11+
12+
# Motivation
13+
14+
The `#[must_use]` lint is extremely useful for ensuring that values
15+
that are likely to be important are handled, even if by just
16+
explicitly ignoring them with, e.g., `let _ = ...;`. This expresses
17+
the programmers intention clearly, so that there is less confusion
18+
about whether, for example, ignoring the possible error from a `write`
19+
call is intentional or just an accidental oversight.
20+
21+
Rust has got a lot of mileage out connecting the `#[must_use]` lint to
22+
specific types: types like `Result`, `MutexGuard` (any guard, in
23+
general) and the lazy iterator adapters have narrow enough use cases
24+
that the programmer usually wants to do something with them. These
25+
types are marked `#[must_use]` and the compiler will print an error if
26+
a semicolon ever throws away a value of that type:
27+
28+
```rust
29+
fn returns_result() -> Result<(), ()> {
30+
Ok(())
31+
}
32+
33+
fn ignore_it() {
34+
returns_result();
35+
}
36+
```
37+
38+
```
39+
test.rs:6:5: 6:11 warning: unused result which must be used, #[warn(unused_must_use)] on by default
40+
test.rs:6 returns_result();
41+
^~~~~~~~~~~~~~~~~
42+
```
43+
44+
However, not every "important" (or, "usually want to use") result can
45+
be a type that can be marked `#[must_use]`, for example, sometimes
46+
functions return unopinionated type like `Option<...>` or `u8` that
47+
may lead to confusion if they are ignored. For example, the `Result<T,
48+
E>` type provides
49+
50+
```rust
51+
pub fn ok(self) -> Option<T> {
52+
match self {
53+
Ok(x) => Some(x),
54+
Err(_) => None,
55+
}
56+
}
57+
```
58+
59+
to view any data in the `Ok` variant as an `Option`. Notably, this
60+
does no meaningful computation, in particular, it does not *enforce*
61+
that the `Result` is `ok()`. Someone reading a line of code
62+
`returns_result().ok();` where the returned value is unused
63+
cannot easily tell if that behaviour was correct, or if something else
64+
was intended, possibilities include:
65+
66+
- `let _ = returns_result();` to ignore the result (as
67+
`returns_result().ok();` does),
68+
- `returns_result().unwrap();` to panic if there was an error,
69+
- `returns_result().ok().something_else();` to do more computation.
70+
71+
This is somewhat problematic in the context of `Result` in particular,
72+
because `.ok()` does not really (in the authors opinion) represent a
73+
meaningful use of the `Result`, but it still silences the
74+
`#[must_use]` error.
75+
76+
These cases can be addressed by allowing specific functions to
77+
explicitly opt-in to also having important results, e.g. `#[must_use]
78+
fn ok(self) -> Option<T>`. This is a natural generalisation of
79+
`#[must_use]` to allow fine-grained control of context sensitive info.
80+
81+
# Detailed design
82+
83+
If a semicolon discards the result of a function or method tagged with
84+
`#[must_use]`, the compiler will emit a lint message (under same lint
85+
as `#[must_use]` on types). An optional message `#[must_use = "..."]`
86+
will be printed, to provide the user with more guidance.
87+
88+
```rust
89+
#[must_use]
90+
fn foo() -> u8 { 0 }
91+
92+
93+
struct Bar;
94+
95+
impl Bar {
96+
#[must_use = "maybe you meant something else"]
97+
fn baz(&self) -> Option<String> { None }
98+
}
99+
100+
fn qux() {
101+
foo(); // warning: unused result that must be used
102+
Bar.baz(); // warning: unused result that must be used: maybe you meant something else
103+
}
104+
```
105+
106+
107+
# Drawbacks
108+
109+
This adds a little more complexity to the `#[must_use]` system, and
110+
may be misused by library authors (but then, many features may be
111+
misused).
112+
113+
The rule stated doesn't cover every instance where a `#[must_use]`
114+
function is ignored, e.g. `(foo());` and `{ ...; foo() };` will not be
115+
picked up, even though it is passing the result through a piece of
116+
no-op syntax. This could be tweaked. Notably, the type-based rule doesn't
117+
have this problem, since that sort of "passing-through" causes the
118+
outer piece of syntax to be of the `#[must_use]` type, and so is
119+
considered for the lint itself.
120+
121+
`Result::ok` is occasionally used for silencing the `#[must_use]`
122+
error of `Result`, i.e. the ignoring of `foo().ok();` is
123+
intentional. However, the most common way do ignore such things is
124+
with `let _ =`, and `ok()` is rarely used in comparison, in most
125+
code-bases: 2 instances in the rust-lang/rust codebase (vs. nearly 400
126+
text matches for `let _ =`) and 4 in the servo/servo (vs. 55 `let _
127+
=`). See the appendix for a more formal treatment of this
128+
question. Yet another way to write this is `drop(foo())`, although
129+
neither this nor `let _ =` have the method chaining style.
130+
131+
Marking functions `#[must_use]` is a breaking change in certain cases,
132+
e.g. if someone is ignoring their result and has the relevant lint (or
133+
warnings in general) set to be an error. This is a general problem of
134+
improving/expanding lints.
135+
136+
# Alternatives
137+
138+
- Adjust the rule to propagate `#[must_used]`ness through parentheses
139+
and blocks, so that `(foo());`, `{ foo() };` and even `if cond {
140+
foo() } else { 0 };` are linted.
141+
142+
- Provide an additional method on `Result`, e.g. `fn ignore(self) {}`, so
143+
that users who wish to ignore `Result`s can do so in the method
144+
chaining style: `foo().ignore();`.
145+
146+
# Unresolved questions
147+
148+
- Are there many other functions in the standard library/compiler
149+
would benefit from `#[must_use]`?
150+
- Should this be feature gated?
151+
152+
# Appendix: is this going to affect "most code-bases"?
153+
154+
(tl;dr: unlikely.)
155+
156+
@mahkoh stated:
157+
158+
> -1. I, and most code-bases, use ok() to ignore Result.
159+
160+
Let's investigate.
161+
162+
I sampled 50 random projects on [Rust CI](http://rust-ci.org), and
163+
grepped for `\.ok` and `let _ =`.
164+
165+
## Methodology
166+
167+
Initially just I scrolled around and clicked things, may 10-15, the
168+
rest were running this JS `var list = $("a");
169+
window.open(list[(Math.random() * list.length) | 0].href, '_blank')`
170+
to open literally random links in a new window. Links that were not
171+
projects (including 404s from deleted projects) and duplicates were
172+
ignored. The grepping was performed by running `runit url`, where
173+
`runit` is the shell function:
174+
175+
```bash
176+
function runit () { cd ~/tmp; git clone $1; cd $(basename $1); git grep '\.ok' | wc -l; git grep 'let _ =' | wc -l; }
177+
```
178+
179+
If there were any `ok`s, I manually read the grep to see if they were
180+
used on not.
181+
182+
## Data
183+
184+
| repo | used `\.ok` | unused `\.ok` | `let _ =` |
185+
|------|-------------|---------------|-----------|
186+
| https://github.com/csherratt/obj | 9 | 0 | 1 |
187+
| https://github.com/csherratt/snowmew | 16 | 0 | 0 |
188+
| https://github.com/bluss/petulant-avenger-graphlibrary | 0 | 0 | 12 |
189+
| https://github.com/uutils/coreutils | 15 | 0 | 1 |
190+
| https://github.com/apoelstra/rust-bitcoin/ | 5 | 0 | 3 |
191+
| https://github.com/emk/abort_on_panic-rs | 0 | 0 | 1 |
192+
| https://github.com/japaric/parallel.rs | 2 | 0 | 0 |
193+
| https://github.com/phildawes/racer | 15 | 0 | 0 |
194+
| https://github.com/zargony/rust-fuse | 7 | 7 | 0 |
195+
| https://github.com/jakub-/rust-instrumentation | 0 | 0 | 2 |
196+
| https://github.com/andelf/rust-iconv | 14 | 0 | 0 |
197+
| https://github.com/pshc/brainrust | 25 | 0 | 0 |
198+
| https://github.com/andelf/rust-2048 | 3 | 0 | 0 |
199+
| https://github.com/PistonDevelopers/vecmath | 0 | 0 | 2 |
200+
| https://github.com/japaric/serial.rs | 1 | 0 | 0 |
201+
| https://github.com/servo/html5ever | 14 | 0 | 1 |
202+
| https://github.com/sfackler/r2d2 | 8 | 0 | 0 |
203+
| https://github.com/jamesrhurst/rust-metaflac | 2 | 0 | 0 |
204+
| https://github.com/arjantop/rust-bencode | 3 | 0 | 1 |
205+
| https://github.com/Azdle/dolos | 0 | 2 | 0 |
206+
| https://github.com/ogham/exa | 2 | 0 | 0 |
207+
| https://github.com/aatxe/irc-services | 0 | 0 | 5 |
208+
| https://github.com/nwin/chatIRC | 0 | 0 | 8 |
209+
| https://github.com/reima/rustboy | 1 | 0 | 2 |
210+
211+
These had no matches at all for `.ok` or `let _ =`:
212+
213+
- https://github.com/hjr3/hal-rs,
214+
- https://github.com/KokaKiwi/lua-rs,
215+
- https://github.com/dwrensha/capnpc-rust,
216+
- https://github.com/samdoshi/portmidi-rs,
217+
- https://github.com/PistonDevelopers/graphics,
218+
- https://github.com/vberger/ircc-rs,
219+
- https://github.com/stainless-steel/temperature,
220+
- https://github.com/chris-morgan/phantom-enum,
221+
- https://github.com/jeremyletang/rust-portaudio,
222+
- https://github.com/tikue/rust-ml,
223+
- https://github.com/FranklinChen/rust-tau,
224+
- https://github.com/GuillaumeGomez/rust-GSL,
225+
- https://github.com/andelf/rust-httpc,
226+
- https://github.com/huonw/stable_vec,
227+
- https://github.com/TyOverby/rust-termbox,
228+
- https://github.com/japaric/stats.rs,
229+
- https://github.com/omasanori/mesquite,
230+
- https://github.com/andelf/rust-iconv,
231+
- https://github.com/aatxe/dnd,
232+
- https://github.com/pshc/brainrust,
233+
- https://github.com/vsv/rustulator,
234+
- https://github.com/erickt/rust-mongrel2,
235+
- https://github.com/Geal/rust-csv,
236+
- https://github.com/vhbit/base32-rs,
237+
- https://github.com/PistonDevelopers/event,
238+
- https://github.com/untitaker/rust-atomicwrites.
239+
240+
Disclosure, `snowmew` and `coreutils` were explicitly selected after
241+
recognising their names (i.e. non-randomly), but this before the
242+
`runit` script was used, and before any grepping was performed in any
243+
of these projects.
244+
245+
The data in R form if you wish to play with it yourself:
246+
```r
247+
structure(list(used.ok = c(9, 16, 0, 15, 5, 0, 2, 15, 7, 0, 14,
248+
25, 3, 0, 1, 14, 8, 2, 3, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
249+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), unused.ok = c(0,
250+
0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0,
251+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
252+
0, 0, 0, 0, 0, 0, 0), let = c(1, 0, 12, 1, 3, 1, 0, 0, 0, 2,
253+
0, 0, 0, 2, 0, 1, 0, 0, 1, 0, 0, 5, 8, 2, 0, 0, 0, 0, 0, 0, 0,
254+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), .Names = c("used.ok",
255+
"unused.ok", "let"), row.names = c(NA, -50L), class = "data.frame")
256+
```
257+
258+
## Analysis
259+
260+
I will assume that a crate author uses *either* `let _ =` or `\.ok()`
261+
for ignoring `Result`s, but not both. The crates with neither `let _
262+
=`s nor unused `.ok()`s are not interesting, as they haven't indicated
263+
a preference either way. Removing those leaves 14 crates, 2 of which
264+
use `\.ok()` and 12 of which use `let _ =`.
265+
266+
The null hypothesis is that `\.ok()` is used at least as much as `let
267+
_ =`. A one-sided binomial test (e.g. `binom.test(c(2, 12),
268+
alternative = "less")` in R) has p-value 0.007, leading me to reject
269+
the null hypothesis and accept the alternative, that `let _ =` is used
270+
more than `\.ok`.
271+
272+
(Sorry for the frequentist analysis.)

0 commit comments

Comments
 (0)