Catching the mismatch
Catching the mismatch
Posted Feb 11, 2025 11:44 UTC (Tue) by josh (subscriber, #17465)In reply to: Catching the mismatch by farnz
Parent article: Maintainer opinions on Rust-for-Linux
One of the most common cases for it: you need to pass a closure accepting an argument, and you want to ignore the argument: |_| do_thing()
Posted Feb 11, 2025 12:30 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (13 responses)
I understand the reasons for this, and why it's also a useful distinction to be able to make, but I've also seen people get caught out by it because they're trying to copy locking habits from other languages, and write let _ = structure.mutex.lock(); expecting that this means they hold the mutex - where mutex has type mutex: Mutex<()> to imitate a plain lock from another language.
Posted Feb 11, 2025 12:48 UTC (Tue)
by heftig (subscriber, #73632)
[Link] (2 responses)
Posted Feb 11, 2025 14:05 UTC (Tue)
by khim (subscriber, #9252)
[Link]
Have you submitted a request? Clippy is not supposed to be “opionated by default”, but it is Ok for it to have “opionated lints” (as long as they are not default) and that one sounds both useful (for some people) and easy to implement.
Posted Feb 11, 2025 14:22 UTC (Tue)
by farnz (subscriber, #17727)
[Link]
Posted Feb 11, 2025 13:56 UTC (Tue)
by adobriyan (subscriber, #30858)
[Link] (9 responses)
Posted Feb 11, 2025 14:46 UTC (Tue)
by Wol (subscriber, #4433)
[Link]
If you give a crossbow-man a musket, of course he's going to try to shoot his foot off :-)
Cheers,
Posted Feb 11, 2025 15:36 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (7 responses)
And it's unhealthy to not talk about the footguns in a language you like - just because you like it doesn't mean it's completely perfect :-)
Posted Feb 11, 2025 15:45 UTC (Tue)
by adobriyan (subscriber, #30858)
[Link] (6 responses)
Posted Feb 11, 2025 15:59 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (1 responses)
Posted Feb 11, 2025 20:42 UTC (Tue)
by mathstuf (subscriber, #69389)
[Link]
Posted Feb 12, 2025 3:58 UTC (Wed)
by geofft (subscriber, #59789)
[Link] (2 responses)
So if you did write let _ = structure.mutex.lock();, yes, you would unlock the mutex immediately, but you also wouldn't have the ability to access the data behind the mutex unless you gave a name to the variable. Because Rust prevents you from completely forgetting to lock the mutex and accessing the data without first locking it, it also prevents you from ineffectively locking the mutex and accessing the data after you unlocked it.
Or in other words, there usually isn't a pattern of "get this RAII object and keep it around for its side effect while doing other stuff". Either you get the RAII object and actually reference it in the stuff you're doing, or you're using some non-RAII API like raw bindings to explicit lock() and unlock() calls where automatic drop isn't relevant.
Posted Feb 12, 2025 7:09 UTC (Wed)
by mb (subscriber, #50428)
[Link]
That's true. It's not done like this in the vast majority of cases.
But misusing this wouldn't (and must not) cause UB.
Posted Feb 12, 2025 11:04 UTC (Wed)
by farnz (subscriber, #17727)
[Link]
There isn't such a pattern in idiomatic Rust, but it gets written when you're still thinking in terms of C++ std::mutex or similar facilities from other languages.
And that makes this a very important footgun to call out, since someone who learnt about concurrency using Rust won't even realise this is an issue, while someone who comes from another language will perceive it as Rust's promises around "fearless concurrency" being broken unless they've already been made aware of this risk - or ask a Rust expert to explain their bug.
Posted Feb 12, 2025 8:31 UTC (Wed)
by ralfj (subscriber, #172874)
[Link]
That said, I agree this is quite surprising, and I've been bitten by this myself in the past.
One "interesting" surprise in Rust, however, is that in let bindings, there is a significant difference between let _ = … and let _foo = …; in the former, whatever you put in place of … is dropped immediately, while in the latter, it's dropped when _foo goes out scope (just as it would be if you used foo instead of _foo).
Underscores in Rust
As useful as this can sometimes be, e.g. when you really don't care about a Underscores in Rust
Result
, I wish clippy would suggest using drop(…)
instead of allowing let _ = …
.
Underscores in Rust
There's a lint to help with that - #![warn(let_underscore_drop)] will catch all the footgun cases where you've used let _ =. It won't stop you using it completely, just in the cases where there's a risk of changed behaviour due to the rules around drop timing.
Underscores in Rust
Underscores in Rust
Underscores in Rust
Wol
All usable languages have footguns - if they didn't, they'd also be blocking you from doing something useful.
Underscores in Rust
Underscores in Rust
It's very specifically a special case where you name a let binding _; you can't read it (_ isn't a real variable), and it drops anything bound to it immediately. _foo and foo behave in exactly the same way, however.
Underscores in Rust
Underscores in Rust
It's not quite as bad as you might think from the above example about a mutex, because a Rust-y mutex API (such as std::sync::Mutex) returns a guard object, which holds the mutex locked until it's dropped, and there's no way to get to the value protected by the mutex without having a guard object. (That is, if you want a mutex-protected structure, you write it as a mutex object that wraps the rest of the data, to make you deal with the mutex before getting to the data, as opposed to a structure with several members, one of which is the mutex that protects the other members, where you can easily bypass the mutex intentionally or unintentionally.) Usually this is implemented via the Deref/DerefMut traits, where you can treat the guard object as a smart pointer and do let mut guard = mutex.lock(); *guard += 1 or whatever, but you can also choose to design an API where the guard object has some sort of methods that return borrowed references to the data. The borrows cannot outlast the guard object, and the mutex is locked so long as the guard object remains in scope.
Underscores in Rust
Underscores in Rust
But there are rare exceptions:
https://docs.rs/tokio/1.43.0/tokio/sync/struct.Semaphore....
Underscores in Rust
Or in other words, there usually isn't a pattern of "get this RAII object and keep it around for its side effect while doing other stuff". Either you get the RAII object and actually reference it in the stuff you're doing, or you're using some non-RAII API like raw bindings to explicit lock() and unlock() calls where automatic drop isn't relevant.
Underscores in Rust