Hacker News new | past | comments | ask | show | jobs | submit login
A Rust match made in hell (fasterthanli.me)
103 points by mooreds on April 30, 2022 | hide | past | favorite | 51 comments



My team has been coding Rust full-time professionally for 4 years and match+locks is literally the only language-level footgun we have encountered after writing many hundreds of thousands of lines of code.

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.


A single language level footgun is a pretty amazing feat, at least relative to the current state of the art.


If we're listing language footguns, I would argue that narrowing 'as' is a footgun.

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.


Yeah, `as` is pretty much "do what C casts do except without UB" (which is basically "do what Java casts do") but/and people are justifiably uncomfortable with it. It’s very likely to go away (be deprecated) at some point as soon as someone comes up with something explicit-but-ergonomic-enough to supersede it. I’d like to see checked, overflowing, wrapping, and saturating alternatives for narrowing coercions, just like there are for arithmetic operations. Float-to-int coercions have the additional and orthogonal checked/truncating/rounding/floor/ceil axis which complicates things.


You can enable a ton more lints:

    #![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


Agreed. It gets even more crazier with int <-> float "casts" that silently change bits.


another related lifetime footgun involving these RAII guard objects is that the two following lines are not the same:

    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.


I thought we had a deny by default lint for dropping a Mutex due to a _ binding. If we don't, definitely should.


This https://godbolt.org/z/rx3h1rxW7 does not even warn you that we're not actually locking the mutex.

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.


On the upper righ side, click on tools and select clippy https://play.rust-lang.org/?version=stable&mode=debug&editio... It will produce the following output:

  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.


Firstly, sincerely thanks for telling me how to add Clippy, that would have been a good idea in this context and now I'll remember it's there.

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]


tbh I would use (and I think have used) drop() for this just because I know drop exists and what it does but Mutex::unlock is something I'd only ever know about if I went and scanned the whole Mutex page on rustdoc.

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.


you're right, there is a clippy lint for it on a mutex in particular. Tbh I think it might be good to generalize it somehow? Because I think there are other cases it could bite you than just mutexes. But obviously the whole point of doing that in the first place is usually to avoid a must-use warning, so it can't be completely general.

Also, imo, that's probably a clippy lint that should be promoted to a built-in.


Rust has far more than one foot gun, though I do think they’re overall still more minor than many languages I’ve used in the past, which is why I love it so much.

I hope future languages have even fewer. Here’s hoping!


A few days ago I wrote: https://news.ycombinator.com/item?id=31143372

> 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.


Part of the reasons lints are in clippy vs rustc, historically, is that clippy lints are more controversial or have too many false positives to live in rustc just yet. So it’s a trade off. If all clippy lints lived in rustc, people would trust the lints less overall.


And as lints show their value in clippy, they do get uplifted to rustc when it makes sense.


Yes, I should have mentioned, this, thank you!


Sure, while this (or any successor) has unavoidable false positives it needs to live in clippy and probably be default off.

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.



An unsolicited tip: it's off-putting to start an article with a rant about your haters. I don't know you at all, but reading that intro makes me care less about what you have to say.


I'm gonna soften that intro. I was definitely riled up when I wrote that and I can express the same sentiment better now - as soon as I'm done tending to the DDoS attacks (sorry, unsollicited load testing) I'm receiving today.

edit: Done removing some salt from the intro, DDoS still flaring up now and then, I'll be busy hardening some more.


Thanks for hearing the feedback. FWIW I think you're putting out good content. Keep writing it for us, not the trolls :)


It's fine, not all of us mind.


> DDoS attacks

I don't understand the drama over criticism concerning a tool.


It's a kid having fun. A kid with a couple botnets, but a kid nonetheless. Definitely not representative of the "Go team" or the "Go community".

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 introduction read like some cartoon villain explaining some sinister plot to paint all the fire hydrants blue, or something.

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.


Yea this was my main problem with this article as well. I started reading because I was interested in what this match made in Hell was, and then 10 minutes into the article I was wondering why I was reading about how Rust works... I understand that sometimes you need to build up to a point, but at least tell me within the first few paragraphs what the overall goal of the article is.

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).


Agreed. Scrollbar shows I got about halfway before I decided he was wasting my time. And found that I was right. It's a bait-and-switch. If you really did need that kind of trickery to promote your language, it would mean something, but it is probably just bad personal judgment.


On this note, I don't read the full text of blog spam. I skip to parts that are interesting which were none in this case, all obvious behaviors.

I make exceptions for Peter Norvig or John Carmack.


Why would he care about "daenz on Hacker News"? It's a bit self-important to think that people should optimize their writing to appeal to specifically you.


This is a great attitude to have if you're trying never to internalize any feedback at all.


Well, given that they dedicated their first few paragraphs to HN commenters, I guess they do care.


This is kind of an area where rust could maybe benefit from being a bit more monadic imo. It would be nice if you could have the MutexGuard object wrap return values of functions called through its AsRef/AsMut impls in a (potentially moved) MutexGuard themselves, so that instead of it relying on borrows and lifetimes to hold the lock it would be the type system itself. I think there's some downsides to this approach, but I've run into other situations where it would have been nice to be able to do this.

I don't believe it's possible with the language as-is though.


Error 522 here -- Cloudflare in Los Angeles reports it cannot reach fasterthanli.me !


Somebody is DDoSing Amos' site, presumably because of all the Go controversy NH generated over the last couple of days


Some fun numbers: 25M requests to fasterthanli.me (just the frontpage, the most expensive page to do queries / render templates for), not cached by CloudFlare (they don't cache HTML unless you set up a rule specifically for it), and a ~7Gb/s peak to tube.fasterthanli.me (hitting a single video asset)

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.


You should definitely try to enable HTML caching - that's what CDNs are far, and it will the workload much cheaper to serve and an origin server less likely to fall over.

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.


This is good advice! All this stuff is very close to my day job, I just never bothered doing it for my personal website because it withstood the front page several times over the past two years.

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!


“note: this is ABSOLUTELY NOT the "go team" or the "go community" reacting to my article. this is one person, a small discord, or some channers having their fun.

the go team got sad and blocked me, they'd never do a DDoS lol”


That sounds absolutely insane, no way people care that much about their programming languages. Do you have some evidence or is this a pipe dream?



Which controversy?



This is tl;dr. As a software engineer who is passionate about reliable systems, I want to love Rust, but when it is this hard to reason about (not the footgun he promised, and I didn't even get to, but the first 3/4 of his article) then I can't get into it, and can't recommend companies invest in it.


Locks are not easy to reason about, period. Rust makes it particularly easier; I shudder about doing similar logic in any other language without this level of tooling.


Channels are easy to reason about. If the language that gives me channels asks me to let it manage memory too, then cool, fine with me.


Channels are most definitely not easy to reason about in the slightest. Superficially, they might appear to be on the surface: but they're really not and have innumerable subtle cases and behaviours in the face of various kinds external conditions, scheduling behaviours, etc. They only start to become 'easy' in a language that utterly ignores all other paradigms, interoperability, and performance concerns as in Go.


They're easy to implement; terribly difficult to reason about from a correctness point of view, which is really all that matters in performant multithreaded code.

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.


Which systems language do you recommend?


If anything, locks are hard so if you can do parallel code that doesn't require it (essentially embarrasingly parallel or the like), it's better. It doesn't fit all use cases though.

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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: