Pre-RFC: `#[must_bind]`

To reiterate a point already made in this thread: let _ = foo() does not “drop stuff directly”. It rather does “not bind” anything which may result in dropping the value if it was only a temporary. The most natural way to think about let _ = EXPR; is: it’s exactly the same as an EXPR; statement (and not the same as drop(EXPR)) except for the fact that let _ = EXPR; suppresses must_use warnings.

The reason for why let _ = EXPR; exists in the first place is rather for consistency since _ is an irrefutable pattern (which is of course much more useful in a match statement) and let supports all other kinds of irrefutable patterns, too.

As to why let _ and let _a must be different: That’s because patterns don’t need to move the value they match on but they can also just reference them: There’s ref PAT patterns and ref mut PAT patterns, so you can write something like

fn foo() {
    struct S{field: (bool,T)};
    impl Drop for S {
        fn drop(&mut self) {}
    }
    struct T;
    impl T {
        fn bar(&self) {
            println!("bar");
        }
    }
    let s = S{field: (false,T)};
    
    match s.field {
        (true, ref t) => t.bar(),
        _ => ()
    }
}

The match statement here inspects the field of s without trying to move it (which would be illegal since S implements Drop). Thus the pattern _ cannot do any moving here. You want to always support a default-case with _ in a match like the above and you want {let PAT = EXPR; BODY} to be same as match EXPR { PAT => {BLOCK} }, this the behavior of let _.

There could is a point to be made that ref patterns are just as confusing as let _ and both should be forbidden in a change that makes let and match always move their value (unless it implements Copy, like when it’s a reference, and with an automatic re-borrow inserted for mutable references). In this case, we could now choose to either have matching against _ behave like drop or like binding to some _a. let _ would either be useless now (when it’s like drop -> so we can disallow it and suggest to just use drop) or it wouldn’t be confusing anymore (if it binds to _a). But this isn’t going to happen anytime soon. One would also need to introduce an alternative mechanism for suppressing must_use, or, I suppose, one could use drop dropping must_use temporaries.

Huge language changes aside, something that we could probably do right now:

I’m not 100% sure on that, but wouldn’t let _ = EXPR; be the same as EXPR; in all the cases without a must_use warning, and the same as drop(EXPR) in all must_use cases? I suppose we could just add warnings against every let _ = EXPR; and either suggest EXPR; or drop(EXPR) for replacement depending on whether a must_use warning would be generated. In my opinion, if I’m not missing anything, this would make a lot of code a lot clearer.

10 Likes