While Rust won't catch the use of subtraction in an arbitrary function named "add", it'll catch (in clippy) the use of subtraction in an implementation of the Add or AddAssign traits: <a href="https://rust-lang.github.io/rust-clippy/master/#suspicious_arithmetic_impl" rel="nofollow">https://rust-lang.github.io/rust-clippy/master/#suspicious_a...</a>
I think the rust making you aware of what you need to do is a good thing. I have a vague memory of writing C code when I was a teenager where I directly manipulated a string from argv[] doing some concatenation or other and it worked fine until it didn't because I was merrily stomping on memory I didn't own (I was thinking in Pascal terms while writing C code). The whole String vs &str thing in Rust can feel a bit fiddly at times, but it does make you aware of what's happening (and I've found that it has improved my JVM coding as well although I do have to occasionally remind myself that I don't need to worry about borrows and lifetimes).
Deadlock detection is an interesting problem. As the author points out, Rust will not detect this at compile time, but there are run time analyzers.<p>It's worth looking at deadlock detection at compile time today. Not just to prevent bugs. Sometimes, on some CPUs, the compiler could potentially turn some mutexes into fence operations and become "lock free".<p>In Rust, code tends to do a lot of scope-based locking. There's a lot of<p><pre><code> item.lock().unwrap().dosomething()
</code></pre>
Now, if the compiler detects that 1) every lock on <i>item</i> is scoped, and 2) everything done inside the lock is short and non-blocking, then that lock could potentially be optimized into fence instructions. Worth thinking about.<p>There's an analogous case for RefCell items. RefCell is a lot like Mutex, except that you call "borrow" instead of "lock", and if the item is ever "busy", waiting won't help, because your own thread made it busy. If every use is a scoped borrow and there are no inner borrows of the same object within the scope of the outer borrow, that's the good case. Reference counts aren't needed at all.<p>I think that somewhere in this space lies the solution to the back-reference problem, but I haven't gotten there yet.
Near the end, the blog posts mentions patterns to avoid accidental deadlocks in structs that wrap a Mutex. Here's another one that I like use:<p><pre><code> struct FooInter {
actual_data: HashMap<...>,
}
impl FooInner {
fn do_something(&mut self) { ... }
}
pub struct Foo(RwLock<FooInner>);
impl Foo {
pub fn do_something(&self) {
self.0.write().do_something();
}
}
</code></pre>
The rule is that FooInner doesn't know about the lock (so calling other methods is safe), while Foo methods can't call other Foo methods (so you can't lock anything twice). You can even move Foo and FooInner to different modules to hide FooInner's contents from Foo, though I rarely find that necessary.<p>I know this is subjective, but for me, this works better than what the blog post suggests -- at least, I haven't gotten it wrong by accident yet.
"I guess that's why the Go compiler is so fast - it barely checks for anything!"<p>Any serious Go programmer ought to have golangci-lint or equivalent running somewhere. I prefer pre-commit, but CI/CD will do too.<p>An advantage of golangci-lint is that is not in the core compiler, so you can get some <i>really</i> opinionated linters from the community, some of which you're going to love and some of which you're going to hate, or linters written really quickly and hammered out in the real world rather than waiting for the full release cycle.<p>A disadvantage of golangci-lint is that it's not in the core compiler, so you have to actually fetch it yourself, maintain it (configure it), etc.<p>With golangci-lint, Go is still not Rust, of course, but it gets it a pretty decent way towards being a serious, quality language. Most languages need some augmenting to be serious, quality languages, because it isn't generally appropriate to put <i>all</i> the requirements for code into the compiler, for many reasons. Rust is somewhat exceptional in how much they've put in their compiler. Things like C and C++ need immense, high-quality support from static analyzers to be even remotely safe to use on a network. (So, yes, I don't consider C or C++ "serious, quality" languages in the absence of that static support. I don't even consider it close, honestly. I don't consider very many languages to be serious, quality languages out of the box.)
Something to note about the JS examples, if you throw them into TypeScript you'll get similar error messages to go vet and rustc.<p>The first example: <a href="https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMOcAUMCUiDeBYAKEUQgQGc4AbAUwDpK4BzNAIhThYBpEsBuQgL6FCoSLASIARgEMATmmz4iJclToNmLGbICELTPwJCCI8NHhIAttJhgFuQsRSzEGRAF5EABl49EAHkQAZl8YAGowxUdiZFQMA2jjYllqKBBZMENibQVDY0JrW1ygA" rel="nofollow">https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMO...</a><p><pre><code> Unreachable code detected
</code></pre>
Along with some typos since `i` isn't declared.
I wonder if there's a future where you have a language model that actually inspects the name of your functions, does an automatic code-gen of them from the context and then compares your implementation and gives you lint errors like:<p><pre><code> fn add() { ... }: mispleading name or incorrect implementation `a - b`
</code></pre>
You know, once upon a time I would have thought that was categorically impossible, but now, you know, it actually seems like something that might not be that far off.<p>> It's just not something Rust is designed to protect against - at least, not today.<p>...maybe, one day. :)
Seeing the RwLock deadlock surprised me. I tried making the standard library's implementation deadlock instead and I'm unable to reproduce it (even with 1 million iterations). The parking_lot implementation does seem to deadlock however.<p>I suppose the parking_lot implementation elides some checks for the sake of performance.<p>EDIT: It seems it can happen with the standard library's RwLock too[1]. Perhaps it's a Linux peculiarity?<p>[1]: <a href="https://doc.rust-lang.org/std/sync/struct.RwLock.html" rel="nofollow">https://doc.rust-lang.org/std/sync/struct.RwLock.html</a>
I’d just like to take a moment to compliment the author on repeatedly producing content that is deeply technical yet very entertaining. As someone who has used both Rust and Go in anger, I enjoyed this one and it’s predecessors a lot. A big step up from a lot of the typical blogspam.
> add("foo".to_string(), "bar")<p>> It's a pretty radical design choice, to force you to be aware that, yeah, since you're creating a new value (out of the two parameters), you will have to allocate. And so it forces you to allocate outside of the Add operation itself.<p>Forcing you to think about allocation is good for many situations.<p>Making you do an <i>extra</i> allocation is a bad way to do that. And the only way I can think of to avoid that would give up having the function be generic.
Wow, what a horrid and cool way to create a function that accepts generic types in Go :-)<p><pre><code> func add(a interface{}, b interface{}) interface{} {
if a, ok := a.(int); ok {
if b, ok := b.(int); ok {
return a + b
}
}
if a, ok := a.(string); ok {
if b, ok := b.(string); ok {
return a + b
}
}
panic("incompatible types")
}</code></pre>
This is a very long post! The bit that actually addresses the title, after all the "here's a thing I tried, here's the weird error message, here's the mistake I made" business, starts way down towards the bottom, with this text:<p><i>Before we move on, I'd like to congratulate you on reading this far. Believe it or not, you're in the minority!</i><p>The title is very misleading when the content is a very, very general discussion of compile-time versus runtime errors, mostly focusing on problems that Go doesn't catch at compile time.<p>TL;DR: the mistake Rust doesn't catch is recursive mutex locks.<p>It does seem like it would be good to catch that one, and that the borrow checker in some form ought to be able to do it -- it should be able to track the static scope of a mutex lock, and block further attempts to lock it inside that scope.