A Rust match made in hell
fasterthanli.me
fasterthanli.me
Relative to the footguns in other languages, it's pretty minor. But it's definitely jarring when you've been spoiled to expect zero footguns.
Rust's 'as' is a general cast. It will try to do something reasonable to get from one type to the other, but while a few of the casts are totally unremarkable, some others open up too much opportunity to blow your feet off in my opinion (and I am not alone). Most obviously casting from a larger to a smaller integer is silently truncating. Sometimes, often even, this is what you intended, sometimes it's unimportant, but once in a while it's a trap.
Obviously it's nothing compared to the landmines in some languages, but I think there's room to teach safer options here, warn for and eventually (in a future edition) prohibit the narrowing cast.
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
This might be too annoying for some people, but you can choose to allow ones that don't matter and keep the rest.Notably: it will warn for as truncation, integer arithmetic (a+b can panic or overflow; use checked_/wrapping_/saturating_), calling a function in unwrap_or, etc.
Lints: https://rust-lang.github.io/rust-clippy/master/index.html
let _ = mutex.lock(); // un-used, un-named 'hole' variable
let _a = mutex.lock(); // un-used, but named 'hole' variable
You might use this when the lock is essentially virtual, guarding something that isn't a data object. The first line will immediately drop the lockguard, while the second will leave it existing until the end of the scope. It's fairly subtle that this is the case.Where I actually ran into this was with a profiling timer object that was returning suspiciously short timings. But it could just as easily have been a critical section of some sort.
There's a correct warning if call lock() without using the result, because it has the must-use annotation, but if we throw it into _ the compiler is satisfied even though that's definitely not what a Mutex guard is for.
error: non-binding let on a synchronization lock
--> src/main.rs:5:5
|
5 | let _ = m.lock();
| ^^^^^^^^^^^^^^^^^
|
= note: `#[deny(clippy::let_underscore_lock)]` on by default
= help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
Clippy is a third party tool, but highly recommended because of things like this. I would like to see this lint uplifted into rustc.Secondly, yes, I think should be uplifted. I'd even argue for default deny because I can't think of a reason you would want this behaviour and yet not find this more explicit phrasing more maintainable:
let guard = m.lock();
Mutex::unlock(guard); // We don't hold the guard, just touch and go
[I'm assuming anybody who actually wants this would prefer Mutex::unlock() over drop() because it says what's going on, and if they care that they can't write this in stable their effort would be appreciated pushing Mutex::unlock stabilisation rather than blocking a default-deny]I like the idea of these sorts of "drop-with-semantics" functions, but in practice until/unless they add a way to `impl !Drop` (or maybe `#[deprecated]` on a trait impl so you can add a deprecation note on your `impl Drop`) on a type, I'm not sure they're worth it.
Also, imo, that's probably a clippy lint that should be promoted to a built-in.
I hope future languages have even fewer. Here’s hoping!
> Everywhere that a clippy lint is there to catch bugs, I wish it wasn't a clippy lint because it won't always get run and those bugs will get missed.
This article in part tells the same story. Ideally - as well as preventing false positives - this "lint" would instead be in Rust's compiler so you cannot do this wrong. Do you want to hold the mutex guard while asleep? No, you do not want to do that.
Doing that's a bunch of work, but part of the reason Rust has been successful up to this point and I believe has more success ahead of it is people being willing to do the work, and not just shrug and say it's too hard.
But I'd argue that correctness is so worthwhile that even if this (or an alternative) can be narrowed to merely avoidable false positives that's enough to justify an on-by-default correctness error.
edit: Done removing some salt from the intro, DDoS still flaring up now and then, I'll be busy hardening some more.
I don't understand the drama over criticism concerning a tool.
If I had to guess, they don't care about Go, they just thought I'd be a fun target (with me generally being a tryhard and everything).
The article reads like:
1. "People hate me because I point out that Rust is better than your language,"
2. "Let's look at a problem I encountered in Rust,"
3. A review of how basic stuff works in Rust like enums, if/else, tangents about inlay hints & IDE support...
Somewhere, if I scroll long enough, I'll get to a section of the article that's not written by Snidely Whiplash, that's not remedial "Rust 101", and not some miscellaneous tangent about Rust or IDE support.
Probably. I'm just guessing, because people are talking about some substantive point that the article made and I just can't make it that far. You can't even scan the article for headings effectively, because they don't stand out.
Also, I feel like people who program in Rust are constantly telling everyone why you should switch to Rust. It's just weird to me. A programming language is a tool, and if you've found a great tool I get being excited about it and wanting people to share in that enjoyment. But it's also annoying when they're constantly trying to convince me that I have problems with my current language of choice, even if I'm perfectly content with my language of choice. Like, I don't see python users or C users going around proselytizing to the general public. Are there any other programming languages that carry this kind of preachy attitude with them? (I can't think of a better word to describe it).
I make exceptions for Peter Norvig or John Carmack.
I don't believe it's possible with the language as-is though.
Like any other DDoS, the goals are 1) take the site down temporarily, 2) cost me some money, 3) try to get whichever provider I'm using to boot me off their network.
They've achieved 1) for a few hours off today, the rest has been fairly entertaining honestly.
Besides that, looking at the Rust code for the origin you link on twitter, you should make some changes to make it reliable at scale. First of all, I recommend to add timeouts. Otherwise the amount of open sockets will just creep up if RSTs got lost/dropped and there is no data to send - which ultimately makes things prone to resource exhaustion.
Also be aware that the tower ConcurrencyLimitLayer alone is not a great solution for this problem - it will build a queue of requests and if clients don't give up the queue gets longer and longer until also no current requests are served anymore and clients will again time out (=> website becomes unreachable). It's better to reject requests fast once a limit is reached than to build infinite queues.
Regarding observability, one can log the amount of processed requests, connections, active requests and connections (to determine which things are stuck), maybe status codes. All these things should require emitting one datapoint per minute or so, which is cheap.
I've done most of what you mentioned (minus load shedding & connection metrics) and have posted about on Twitter, if you want to check out the thread again!
the go team got sad and blocked me, they'd never do a DDoS lol”
Given that it can take weeks to debug a deadlock, I am more than happy to pay the price of some boilerplate and difficult types if that helps correctness.
Honestly, if things are slower, not really mission critical for speed, but are easier to reason about, it's probably worth it to keep it simple and even sequential.