|
| 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