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

Warn on useless bindings like let v2 = v1.sort(); #71432

Closed
mbrubeck opened this issue Apr 22, 2020 · 15 comments · Fixed by #112380
Closed

Warn on useless bindings like let v2 = v1.sort(); #71432

mbrubeck opened this issue Apr 22, 2020 · 15 comments · Fixed by #112380
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@mbrubeck
Copy link
Contributor

mbrubeck commented Apr 22, 2020

I've seen a lot of beginning Rust programmers make a mistake similar to the program below, which compiles and runs without warning:

fn main() {
    let mut x = [0,1,2];
    x[0] = 5;
    let y = x.sort();
    println!("{:?}", y);
}

The bug in this program is hard for new Rust programmers to spot. The compiler should help them, by warning that x.sort() does not return a value.

This could be done with a new attribute #[must_not_use], applied to functions like sort. This would be the inverse of must_use: It would warn only if the result of the function is used. Like must_use, it would take an optional argument that provides an additional diagnostic message.

(Bikeshed: must_not_use may be a confusing name. Other suggestions welcome.)

Or perhaps any assignment let foo = expr; where expr has type () should cause a warning by default. This would be a more general solution, catching more possible errors but also potentially causing more disruption. The warnings in this case might be less helpful, because they wouldn't include suggestions tailored to specific functions.

@mbrubeck mbrubeck added the A-diagnostics Area: Messages for errors, warnings, and lints label Apr 22, 2020
@jonas-schievink jonas-schievink added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 22, 2020
@Mark-Simulacrum
Copy link
Member

It seems like we at least want the let y = { expr evaluating to () }; warning, since that's entirely useless AFAICT.

@comex
Copy link
Contributor

comex commented Apr 22, 2020

It could possibly occur legitimately due to a macro, though. For example, this silly macro doesn't care whether the expression returns () or a real value:

macro_rules! say_hello_after_evaluating_expr {
    ($expr:expr) => { {
        let ret = $expr;
        println!("hello!");
        ret
    } }
}
fn main() {
    let forty_two = say_hello_after_evaluating_expr!(42);
    say_hello_after_evaluating_expr!(println!("this returns ()!"));
}

@Nokel81
Copy link
Contributor

Nokel81 commented Apr 22, 2020

Well, in that situation the macro writer could just do a local allow.

@cuviper
Copy link
Member

cuviper commented Apr 22, 2020

How about nested () -- would you also warn on this?

fn foo() -> io::Result<()> { ... }
fn main() {
    if let Ok(x) = foo() { ... }
}

IOW, is this just about the RHS being (), or about binding a name to () at all?

@Mark-Simulacrum
Copy link
Member

I think this is about binding a name to (), there's not really a good reason to do so IMO.

Maybe the lint would then be less about the "this function returns unit" (though that could be a special case to provide better messaging on), but more so that any binding with a type of () is linted on.

I'm trying to think of what exactly makes () special -- e.g. it seems like ((), ()) should also be linted against -- but not really coming up with anything concrete. Certainly ZST + Copy isn't right.

@mbrubeck
Copy link
Contributor Author

mbrubeck commented Apr 22, 2020

I think it would be reasonable to warn on any binding to (). If the program never reads from the binding, then it already triggers an unused_variables warning. If it does read from it, this is probably a bug and would benefit from a warning.

Though any solution that captures simple cases like my original example is probably sufficient to provide most of the benefit here.

I'm trying to think of what exactly makes () special -- e.g. it seems like ((), ()) should also be linted against -- but not really coming up with anything concrete.

() may not be uniquely special, but because it is the default return type for Rust functions and blocks, and is supposed to be used for all expressions that exist only for their side effects, it's probably the only such type that matters in practice for 99% of programs. I see little practical benefit from trying to catch every possible case where a return value is useless. Types like ((), ()) show up mainly in generic or macro code where this lint wouldn't be useful, anyway.

@Mark-Simulacrum
Copy link
Member

I definitely agree that catching just unit is probably enough. I was mostly trying to come up with some cases where I also want this for non-unit types -- but I was unable to do so.

I'm going to nominate this issue for lang to hopefully gauge whether we want an FCP here or if adding a lint like this (naming to be bikeshedded) needs a full RFC. I personally lean towards maybe an FCP, maybe even just "no one in the room has objections" approval (we'd have some time to roll it back if necessary, of course).

@Mark-Simulacrum Mark-Simulacrum added I-nominated T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Apr 22, 2020
@scottmcm
Copy link
Member

@cuviper
Copy link
Member

cuviper commented Apr 22, 2020

Of course clippy has this covered. There's probably even an xkcd for that.

It was only recently downgraded to pedantic (allowed): rust-lang/rust-clippy#5409

@Mark-Simulacrum
Copy link
Member

I agree with @dtolnay that the bug there (with inference) seems like a hard blocker.

But it seems plausible that we'd perhaps want it in rustc regardless as allow by default to start. Maybe if we throw compiler team at it someone will come up with an idea how to do it (seems related to @nikomatsakis work on the ! type inference).

@cuviper
Copy link
Member

cuviper commented Apr 22, 2020

let _: () = f();

_ is a special binding -- it should be exempt from this. That binding can also be avoided while still affecting type inference: let () = f();. However, clippy continues to lint this. I haven't looked at the code, but it seems like clippy is only looking at the expression, not the pattern bindings.

@moxian
Copy link
Contributor

moxian commented Apr 23, 2020

To bikeshed this further, i would argue that let x: () = ... should not trigger the lint (even with non-_ binding, and even when no inference is involved), on the premise that the author is already aware of the type, and this must be deliberate.

@estebank
Copy link
Contributor

estebank commented Apr 23, 2020

I think that this belongs in rustc (I have argued as much in the past) and the rules should be:

  • Ignore let assignments with a user provided type
  • Ignore macros
  • Ignore _

@joshtriplett
Copy link
Member

To bikeshed this further, i would argue that let x: () = ... should not trigger the lint (even with non-_ binding, and even when no inference is involved), on the premise that the author is already aware of the type, and this must be deliberate.

Agreed. The lint should cover the case where the user doesn't explicitly write the type ().

@varkor varkor added the A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. label Apr 23, 2020
@joshtriplett
Copy link
Member

We discussed this further in today's language team meeting, and came to the following consensus:

  • We should lint on let x = expr; if we infer x as type ().
  • We should suppress that lint if you write the type () explicitly: let x: () = expr;
  • We should do a crater run to see how noisy this will be, before merging it.

bors added a commit to rust-lang-ci/rust that referenced this issue Nov 19, 2023
…ffleLapkin

Add allow-by-default lint for unit bindings

### Example

```rust
#![warn(unit_bindings)]

macro_rules! owo {
    () => {
        let whats_this = ();
    }
}

fn main() {
    // No warning if user explicitly wrote `()` on either side.
    let expr = ();
    let () = expr;
    let _ = ();

    let _ = expr; //~ WARN binding has unit type
    let pat = expr; //~ WARN binding has unit type
    let _pat = expr; //~ WARN binding has unit type

    // No warning for let bindings with unit type in macro expansions.
    owo!();

    // No warning if user explicitly annotates the unit type on the binding.
    let pat: () = expr;
}
```

outputs

```
warning: binding has unit type `()`
  --> $DIR/unit-bindings.rs:17:5
   |
LL |     let _ = expr;
   |     ^^^^-^^^^^^^^
   |         |
   |         this pattern is inferred to be the unit type `()`
   |
note: the lint level is defined here
  --> $DIR/unit-bindings.rs:3:9
   |
LL | #![warn(unit_bindings)]
   |         ^^^^^^^^^^^^^

warning: binding has unit type `()`
  --> $DIR/unit-bindings.rs:18:5
   |
LL |     let pat = expr;
   |     ^^^^---^^^^^^^^
   |         |
   |         this pattern is inferred to be the unit type `()`

warning: binding has unit type `()`
  --> $DIR/unit-bindings.rs:19:5
   |
LL |     let _pat = expr;
   |     ^^^^----^^^^^^^^
   |         |
   |         this pattern is inferred to be the unit type `()`

warning: 3 warnings emitted
```

This lint is not triggered if any of the following conditions are met:

- The user explicitly annotates the binding with the `()` type.
- The binding is from a macro expansion.
- The user explicitly wrote `let () = init;`
- The user explicitly wrote `let pat = ();`. This is allowed for local lifetimes.

### Known Issue

It is known that this lint can trigger on some proc-macro generated code whose span returns false for `Span::from_expansion` because e.g. the proc-macro simply forwards user code spans, and otherwise don't have distinguishing syntax context compared to non-macro-generated code. For those kind of proc-macros, I believe the correct way to fix them is to instead emit identifers with span like `Span::mixed_site().located_at(user_span)`.

Closes rust-lang#71432.
@bors bors closed this as completed in 73bc121 Nov 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.