It is true that Rust is the first major language with both pervasive, deterministic RAII and pattern matching. Kind of wild to be honest! A real marriage of systems and functional code.
That's arguably true, but only insofar as it makes sense to talk about a mutex "owning" "data". There are many kinds of data that can't be owned by the language runtime like this (think about async/shared/DMA buffers, register blocks on foreign hardware, memory-mapped database files), but that still clearly need synchronization. Even things like coarse-grained locks taken at the subsystem level (e.g. to avoid having a separate atomic in every little object) are a bad fit for this kind of API.
There's value to being opinionated about API design, but there's also cost. And Rust is supposed to be playing in the same sandbox as the lower level tools.
Here’s what I understand from your comment, correct me if I’m wrong. You’re saying there is a runtime cost associated with a mutex/rwlock owning the data. And secondly, you think it isn’t possible to implement this pattern in other areas that need locking.
I don’t think either of those is true. Encoding the ownership in the type system makes things clearer, imposes a compile-time cost but not run-time cost. Also, there isn’t any magic in the stdlib implementation of Mutex and Rwlock other than implementing it natively for every OS. This means that it is possible to implement the pattern for the examples you gave.
That is true, but I don't believe Rust imposes a cost here. The worst case degrades to a `Mutex<()>` and then whatever's being guarded being managed separately.
From an API design point of view, I would always encapsulate the logically mutable state in some type `Foo`, even if it doesn't literally own the memory, and then expose a safe API using `Mutex<Foo>`.
Idiomatically, you would then make `Foo::new_unchecked()` unsafe, with the precondition that nobody else is accessing the same external resource, and that `Foo::new_unchecked()` is only ever called once.
From my understanding: The original implementation is consistent with the behavior of `match`. However it was realized that it is both less intuitive than early dropping (as this post suggests) and also gets in the way of understandable semantics for if let chains, so the decision was made to change it in the next edition.
Match has multiple branches, and some or all of them can bind variables, (borrowed from the value matched against), defined in that branch. For that to be possible, it means that the temporary lifetime of the matched value must encompass all the branches.
Compared to that, if let - else has only two branches: one where matching (and possible variable binding) happens, and one where it doesn't. You couldn't have anything borrowed from the matched value in the else branch, so the lifetime extending to encompass the else branch is not strictly necessary.
Also, from a pure syntax perspective, an if let block reads like it creates a binding that exists within the scope of the braces after the if, and a match block reads like it creates a binding that exists within the "scope" of the braces surrounding the entire match block.
These all read the same to me from a scoping perspective:
if let Some(x) = expr {
// expr is live here
}
while let Some(x) = expr {
// expr is live here
}
match expr {
// expr is live here
Some(x) => {},
_ => { /* expr is still live, doesn't matter that x is inaccessible */
}
fn foo(x: i32) {
// same idea, the variable x belongs to the scope it's declared alongside
}
For the scope of an if-let expression to extend into the else block afterwards violates the principle of least surprise for me -- and clearly the author of the OP too!
if let Some(x) = expr {
// expr is live here
} else {
// ??? why is expr live here? the if is clearly "out of scope!"
}
https://github.com/rust-lang/rust/issues/131154 has some discussion about all the possibilities. It's pretty subtle, but I think everyone agrees that the current behavior of if let is unintuitive. Beyond that there are a bunch of possibilities none of which is clearly superior.
https://github.com/rust-lang/rust/issues/131154#issuecomment... Especially this matrix was helpful! The behaviour that's going to be adopted in Rust 2024 is the Rust2024.1 one. They were having doubts about the rules becoming too complex/non-uniform, but at least dropping early means compiler errors rather than deadlocks.
> I am hearing this argument a lot, but I don't really get it. My grocery store forces me to make a lot of decision when it offers me 7 kinds of ketchup and 20+ kinds of cereal, but no one says "I am avoiding large grocery stores because they force me to make too many decisions".
I say that all the time. I hate decision paralysis. I'll specifically buy stuff in ways (such as online order + pickup) that I don't have to look at tons of different products.
As far as I'm concerned this is the only correct way to do it. I believe illumos does this, which is why its env functions are thread safe and have been for decades.
> To be clear this isn't a criticism of Rust's design or implementation - demarcated blocks of unsafe code is pragmatic and sensible. The problem is how humans build software. In this sense I don't think we've really settled whether "rewrite the code in Rust" is actually safer than "redo our technical management to include automated memcheck testing and paired code reviews." At the very least, I don't think the latter is insufficient
I think the latter is woefully insufficient, having seen critical C++ memory safety bugs that got past every sanitizer. For me the debate was settled once our team at Meta shipped a large, high-performance Rust service, built over many years, that simply did not have memory safety bugs -- and its success became a crucial part of why Rust is now a first-class language at Meta.
Ok, I didn't really define logic bugs. I think of things like race conditions as memory bugs because its improper access to the same variable.
So I suppose All Bugs Are Logic Bugs.
But I really meant that many software software vuln aren't even that fancy. Sure if you have something like an iPhone which has whole companies trying to hack it, then eventually the bugs you have left are fancy heap overflows. But lots of products have logic errors, like mischecking creds etc.
I will also pick on CISA for recommending a language that requires something like cargo. Why is it a good idea for critical infrastructure to require internet access to compile its code? CISA is supposed to be concerned about the fact that critical infra. is privately held and they should encourage secure practices. So suggest a language that in practice requires internet access? this is absurd to me.
Cargo does not require internet access to compile. You need it just to download packages once (which you obviously do in any ecosystem). Cargo also cryptographically verifies that downloaded packages haven't been tampered with.
Affine types also help with credential checking! Newtype wrappers synergize really well with them. I wrote a post about some of this a few years ago: https://sunshowers.io/posts/types/
reply