Unexpected Productivity Boost of Rust
Original: Unexpected productivity boost of Rust
Key topics
The Rust programming language is being hailed as a productivity booster, sparking a heated debate about its merits compared to other languages like Python. While some commenters, like koakuma-chan, are adamant that Rust is superior and that Python is "irreversibly flawed," others, such as wolvesechoes and dkdcio, push back against this notion, pointing out that Python's ecosystem is not as undocumented as claimed and that reliable software can be built in any language. A surprising takeaway is that some developers, like veber-alex, suggest that using a type checker like Pyright can get you "80% of Rust's type safety" in Python, muddying the waters on the necessity of switching to Rust. The discussion remains lively, with some commenters, like jvanderbot, attributing Rust's productivity benefits to its strong type checking, while others, like 9question1, joke that AI can now generate documentation, making some of the complaints about Python's ecosystem moot.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
35m
Peak period
90
0-6h
Avg / period
14.5
Based on 160 loaded comments
Key moments
- 01Story posted
Aug 27, 2025 at 11:48 AM EDT
4 months ago
Step 01 - 02First comment
Aug 27, 2025 at 12:22 PM EDT
35m after posting
Step 02 - 03Peak activity
90 comments in 0-6h
Hottest window of the conversation
Step 03 - 04Latest activity
Aug 31, 2025 at 2:01 PM EDT
4 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
I'm a rust dev full time. And I agree with everything here. But I also want people to realize it's not "Just Rust" that does this.
In case anyone gets FOMO.
Please, main Python libraries have much richer docs than almost anything from Rust ecosystem.
Use a type checker! Pyright can get you like 80% of Rust's type safety.
But not equally flawed.
https://www.lesswrong.com/posts/dLJv2CoRCgeC2mPgj/the-fallac...
Nobody would claim that. But are you trying to say that the language has no effect on reliability? Because that's obviously nonsense.
Language choice has some effect on reliability, and I would say Python's effect is mediocre-to-bad. Depending on if you use Pyright. Not too bad if you do. Pretty awful if you don't.
It's not true you can't build reliable software in python. People have. There's proof of it everywhere. Tons of examples of reliable software written in python which is not the safest language.
I think the real thing here is more of a skill issue. You don't know how to build reliable software in a language that doesn't have full type coverage. That's just your lack of ability.
I'm not trying to be insulting here. Just stating the logic:
Just spitting facts.But I can build reliable software without types as well. Many people can. This isn’t secret stuff that only I can do. There are thousands and thousands of reliable software built on Python, ruby and JavaScript.
We had sentry installed so I know exactly how many exceptions were happening, rare to zero. Lots of tests/constraints on the database as well.
That said I like a nice tight straitjacket at other times. Just not every day. ;-).
P.S. Python doesn’t have the billion-dollar-mistake with nulls. You have to purposely set a variable to None.
As a solo dev, I find that I start off in Python, but at a certain project size I find it too unwieldy to manage (i.e. make changes without breaking things) and that's when I implement part or all of the project in Rust.
Yes, exactly. It doesn’t happen that often, but it does.
And folks have forgotten, not sure why, but Python was always billed as a prototyping language in the “olden tymes.” Or even “executable pseudocode.” At those it excels.
https://python.plainenglish.io/how-instagram-uses-python-sca...
mypy's output is, AFAICT, also non-deterministic, and doesn't support a programmatic format that I know of. This makes it next to impossible to write a wrapper script to diff the errors to, for example, show only errors introduced by the change one is making.
Relying on my devs to manually trawl through 80k lines of errors for ones they might be adding in is a lost cause.
Our codebase also uses SQLAlchemy extensively, which does not play well with typecheckers. (There is an extension to aid in this, but it regrettably SIGSEGVs.)
Also this took me forever to understand:
That will get you:Regarding the ~80k errors. Yeah, nothing to do here besides slowly grinding away and adding type annotations and fixes until it's resolved.
For the code example pyright gives some hint towards variance but yes it can be confusing.
https://pyright-play.net/?pyrightVersion=1.1.403&code=GYJw9g...
When done, do typing similarly.
Love this.
Sort of like closing 80% of a submarine's hatches and then diving.
(Also, it means that you don't get any performance benefit from static typing your program.)
(But if you must use Python then definitely use Pyright.)
And that's assuming the codebase and all dependencies have correct type annotations.
https://www.tiobe.com/tiobe-index/
https://pypl.github.io/PYPL.html
https://www.statista.com/statistics/793628/worldwide-develop...
https://redmonk.com/sogrady/2024/03/08/language-rankings-1-2...
The accessibility of Python is overrated. It's a language with warts and issues just like the others. Also the lack of static typing is a real hindrance (yes I know about mypy).
With it Python feels about at the type safety level of Typescript - not as good as a language that had types the whole time, but much much better than nothing if enforced with strict rules in CI.
I don't like JS but after having used TS intermittently for a number of years I'm starting to think JS is the better option. At least there I don't get tricked by typed objects being something other than what they claim to be, or waste time trying to declare the right types for some code that would work perfectly without TS.
TS is too much work for too little reward. I'd rather just make simple frontends with as little logic as possible and do the real programming in a real programming language on the backend.
I use python for some basic scripting and I don't write anything huge. Most of these do roughly what I would expect.
> __new__ is a static method that’s responsible for creating and returning a new instance (object) of the class. It takes the class as its first argument followed by additional arguments.
> In Python, __init__ is an instance method that initializes a newly created instance (object). It takes the object as its first argument followed by additional arguments
> Python id() function returns the “identity” of the object. The identity of an object is an integer, which is guaranteed to be unique and constant for this object during its lifetime.
The pickel.dumps() is the only one that is a bit odd until to find out what the pickle module does.
> The accessibility of Python is overrated.
The accessibility isn't overrated. Python has something that is missing from a lot of languages that isn't often talked about. It is really good a RAD (Rapid Application Development). You can quickly put something together that works reasonably well, it also is enough of the proper language that you can build bigger things in it.
> It's a language with warts and issues just like the others.
Like every other one.
A lot of languages work for RAD including Clojure, C#, and JavaScript. This is nothing special about Python.
By that standard nothing is. At some point if you are using a programming language you are going to have to RTFM. None of things you cherry-picked would be used by a novice either.
Every example you gave are what I call are "Ronsil" (https://en.wikipedia.org/wiki/Does_exactly_what_it_says_on_t...).
Even the pickle.dumps() example is obvious when you read the description for the module and works exactly the same to json.dumps(), which works similarly to dumps() methods and terminology in other programming languages.
I feel like I am repeating myself.
> A lot of languages work for RAD including Clojure, C#, and JavaScript. This is nothing special about Python.
Nonsense. None of those I would say are RAD. JavaScript literally has no standard lib and requires node/npm these days and that can be a complete rigmarole in itself. C# these relies heavily on DI. I have no idea about Clojure so won't comment.
All the RAD stuff in C# and JS is heavily reliant on third party scripts and templates, that have all sorts of annoying quirks and bloat your codebase. That isn't the case with Python at all
Okay, and? I didn't make the claim that some other language was all that. I was dispelling the claim that Python is.
> Even the pickle.dumps() example is obvious
Well, we've so far been restricted to function names which is what the claim was. There are plenty of cryptic other names in Python like ABCMeta, deriving from `object`, MRO, slots, dir, spec, etc.
The idea you can't do RAD with libraries is insane. Games are developed rapidly, and a lot of game engines use C#. The fact that you're using Unity, a very large dependency, means nothing regarding whether you can do RAD, which is more about having the right architecture, tooling, and development cycle.
I believe that people should RTFM. Any arguments that is predicated on not reading the documentation for the language, and then pretending that it is somehow opaque, I am going to dismiss to be quite honest.
> Well, we've so far been restricted to function names which is what the claim was. There are plenty of cryptic other names in Python like ABCMeta, deriving from `object`, MRO, slots, dir, spec, etc.
You are still cherry-picking things to attempt to prove a point. I don't find this convincing.
> The idea you can't do RAD with libraries is insane. Games are developed rapidly, and a lot of game engines use C#. The fact that you're using Unity, a very large dependency, means nothing regarding whether you can do RAD, which is more about having the right architecture, tooling, and development cycle.
I didn't say that you can't do RAD with libraries. You didn't understand what I was saying at all.
I can get up and running with Python in mere minutes. It doesn't require a application templates/scaffolding apps to get started (like C# and JS/TS). You just need a text editor and a terminal. Doing that is still quicker and easier to get something working than all the gumpf you have to do with the other languages. I BTW was a JS/TS and .NET for about 15 years
I just wish there were more Python and Go jobs in the UK.
I feel you on the lack of Go jobs. It seems like they aren't very well globally distributed...
They've changed so much in the last few years I honestly don't know anymore. Which is part of the entire problem.
The last time I bothered writing anything with C# / .NET was .NET 8. They definitely had scaffolding tools for popular project types. Setting stuff up from a blank project wasn't straight forward.
> It comes with a large standard library (aka .NET). Even the NodeJS standard library is quite large now too.
I find dealing with C++ and CMake/Make (I hobby program Vulkan/OpenGL) easier than dealing with Node JS and NPM. People think I am being hyperbolic when I say this, I am not. Which show you how insane the JS ecosystem is.
I am honestly fed up of both C# and JS. There are far more headaches with both (especially if you are using TypeScript).
If you use TypeScript and don't want to use babel, until recently you have to basically use tsx or tsnode . You then have to wrangle a magic set of options in the tsconfig.json to have some popular libraries work.
.NET after 5 has absolute DI madness in ASP.NET and none of it seems documented properly anywhere (or I can't find it) and it seems to change subtly every time they update .NET or ASP.NET.
I ended up resorting to pulling down the entire source code to see what the Startup was doing. C# now has total language and syntactic sugar overload.
I have almost none of these headaches with Python and Go.
> And anyway, RAD isn't about setup time, it's about iteration time.
It is both. I find Python quicker, easier and less headaches that either JS or .NET. I am well versed in C# and JS.
I know less Python than .NET and JS/TS, yet I find it easier.
> I feel you on the lack of Go jobs. It seems like they aren't very well globally distributed...
That is true. I am sure most Go jobs advertised in the UK are in London.
That doesn't need scaffolding either. And the standard library is huge too; you could even add dependencies in that file.
And since we're talking about RAD, Python can't even compare to Clojure. Having a separate REPL "server" that you interact with from your text editor with access to the JVM's ecosystem and standard library inside of a "living" environment and structural navigation from being a LISP is pure RAD. Heck, I often start a REPL "server" inside chrome's devtools with scittle[1] if I need to rapidly and programmatically interact with a website and/or to script something; I haven't been able to do that anywhere else. Even pure JS.
[1]: https://github.com/babashka/scittle
I've just had a quick look at some of this and they've basically just moved stuff in to the cs file from the proj file. I remember them saying this was on the roadmap when I was doing a .NET 8 refresher.
It also seems anything non-trivial will still require proj files. Which means that they are likely to still have project templates etc.> And since we're talking about RAD, Python can't even compare to Clojure.
I am unlikely to ever use Clojure, I certainly won't be able to use it at work.
> Having a separate REPL "server" that you interact with from your text editor with access to the JVM's ecosystem and standard library inside of a "living" environment and structural navigation from being a LISP is pure RAD. Heck, I often start a REPL "server" inside chrome's devtools with scittle[1] if I need to rapidly and programmatically interact with a website and/or to script something; I haven't been able to do that anywhere else. Even pure JS.
All sounds very complicated and is the sort of thing I am trying to get away from. I find all of this stuff more of a hinderance than anything else.
The problem is you will spend your whole life unsuccessfully trying to get your lazy colleagues to actually use them.
Are we talking about the same Python? Have you seen the Python documentation? There's a reason it ranks so badly on Google.
Also you forgot the abysmal performance and laughably janky tooling (until uv saved us from that clusterfuck anyway).
It's really, really good for <1000 LoC day projects that you won't be maintaining. (And, if you're writing entirely in the REPL, you probably won't even be saving the code in the first place.)
The ideal scenario is what you are saying but most of the time it boils down to deadline vs familiarity/skill (of the developer and the team) trade-off.
Happens all the time in my experience. It goes so far that big companies like Facebook, Google and Dropbox have all ended up writing their own Python/PHP runtimes or even entirely new languages like Hack and Google's new C++ thing rather than rewrite, because rewrites become impossible very quickly.
That's why - despite people saying language doesn't matter - it is very important to pick the right language from the start (if you can).
I suppose most of my point was that in the case described one should jump to the rewrite sooner rather than later, to avoid the situation you describe.
https://docs.rs/tokio/latest/tokio/task/fn.spawn.html
If you want to run everything on the same thread then localset enables that. See how the spawn function does not include the send bound.
https://docs.rs/tokio/latest/tokio/task/struct.LocalSet.html
The compiler knows the Future doesn't implement the Send trait because MutexGuard is not Send and it crosses await points.
Then, tokio the aysnc runtime requires that futures that it runs are Send because it can move them to another thread.
This is how Rust safety works. The internals of std, tokio and other low level libraries are unsafe but they expose interfaces that are impossible to misuse.
One caveat though - using a normal std Mutex within an async environment is an antipattern and should not be done - you can cause all sorts of issues & I believe even deadlock your entire code. You should be using tokio sync primitives (e.g. tokio Mutex) which can yield to the reactor when it needs to block. Otherwise the thread that's running the future blocks forever waiting for that mutex and that reactor never does anything else which isn't how tokio is designed).
So the compiler is warning about 1 problem, but you also have to know to be careful to know not to call blocking functions in an async function.
This is simply not true, and the tokio documentation says as much:
"Contrary to popular belief, it is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code."
https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#wh...
there are absolutely situations where tokio's mutex and rwlock are useful, but the vast majority of the time you shouldn't need them
True. I used std::mutex with tokio and after a few days my API would not respond unless I restarted the container. I was under the impression that if it compiles, it's gonna just work (fearless concurrency) which is usually the case.
Or your server is heavily contended enough that all worker threads are blocked on this mutex and no reactor can make forward progress.
- The return type of Mutex::lock() is a MutexGuard, which is a smart pointer type that 1) implements Deref so it can be dereferenced to access the underlying data, 2) implements Drop to unlock the mutex when the guard goes out of scope, and 3) implements !Send so the compiler knows it is unsafe to send between threads: https://doc.rust-lang.org/std/sync/struct.MutexGuard.html
- Rust's implementation of async/await works by transforming an async function into a state machine object implementing the Future trait. The compiler generates an enum that stores the current state of the state machine and all the local variables that need to live across yield points, with a poll function that (synchronously) advances the coroutine to the next yield point: https://doc.rust-lang.org/std/future/trait.Future.html
- In Rust, a composite type like a struct or enum automatically implements Send if all of its members implement Send.
- An async runtime that can move tasks between threads requires task futures to implement Send.
So, in the example here: because the author held a lock across an await point, the compiler must store the MutexGuard smart pointer as a field of the Future state machine object. Since MutexGuard is !Send, the future also is !Send, which means it cannot be used with an async runtime that moves tasks between threads.
If the author releases the lock (i.e. drops the lock guard) before awaiting, then the guard does not live across yield points and thus does not need to be persisted as part of the state machine object -- it will be created and destroyed entirely within the span of one call to Future::poll(). Thus, the future object can be Send, meaning the task can be migrated between threads.
If anything that's a disadvantage. You want your health monitoring to be the canary, not something that keeps on trucking even if the system is no longer doing useful work. (See the classic safety critical software fail of 'I need a watchdog... I'll just feed it regularly in an isolated task')
Or do you want the web server responding healthily & rely on metrics and alerts to let you know that /may_deadlock_server is taking a long time to handle requests / impacting performance? Your health monitoring is an absolute last step for automatically mitigating an issue but it'll only help if the bug is some state stuck in a transient state - if it'll restart into the same conditions leading to the starvation then you're just going to be in an infinite reboot loop which is worse.
Healthz is not an alternative to metrics and alerting - it's a last stopgap measure to try to automatically get out of a bad situation. But it can also cause a worse problem if the situation is outside of the state of the service - so generally you want the service to remain available if a reboot wouldn't fix the problem.
In the Rust community, cancellation is pretty well-established nomenclature for this.
Hopefully the video of my talk will be up soon after RustConf, and I'll make a text version of it as well for people that prefer reading to watching.
Tokio MutexGuards are Send, unfortunately, so they are really prone to cancellation bugs.
(There's a related discussion about panic-based cancellations and mutex poisoning, which std's mutex has but Tokio's doesn't either.)
[1] spawn_local does exist, though I guess most people don't use it.
The generally recommended alternative is message passing/channels/"actor model" where there's a single owner of data which ensures cancellation doesn't occur -- or, at least that if cancellation happens the corresponding invalid state is torn down as well. But that has its own pitfalls, such as starvation.
This is all very unsatisfying, unfortunately.
You can definitely argue that developers should think about await points the same way they think about letting go of the mutex entirely, in case cancellation happens. Are mutexes conducive to that kind of thinking? Practically, I've found this to be very easy to get wrong.
> The exact behavior on locking a mutex in the thread which already holds the lock is left unspecified. However, this function will not return on the second call (it might panic or deadlock, for example).
Rust can use that type information and lifetimes to figure out when it's safe and when not.
A type is “Send” if it can be moved from one thread to another, it is “Sync” if it can be simultaneously accessed from multiple threads.
These traits are automatically applied whenever the compiler knows it is safe to do so. In cases where automatic application is not possible, the developer can explicitly declare a type to have these traits, but doing so is unsafe (requires the ‘unsafe’ keyword and everything that entails).
You can read more at rustinomicon, if you are interested: https://doc.rust-lang.org/nomicon/send-and-sync.html
On the other hand, that strictness is precisely what leads people to end up with generally reasonable code.
Same thing for the borrow-checker.
Adding `return todo!()` works well enough for some cases, but not all, because it can't confirm against impl Trait return types.
And these strategies are things that people need to be taught about, individually. I'm not saying that the current state is terrible, just that there might be things we can do to make them better.
I do think that'd be useful in a variety of cases, especially for testsuites. I don't think I'd want to go as far as trying to guess how to substitute `Arc`/`Mutex`/`RwLock` for a failed borrow, but there are a few different strategies that do seem reasonably safe.
In addition to the automatic todo!() approach, there's the approach of compile-time tainting of branches of the item tree that failed to compile. If something doesn't compile, turn it into an item that when referenced makes the item referencing it also fail to compile. That would then allow any items that do compile to get tested in the testsuite.
> Adding `return todo!()` works well enough for some cases, but not all, because it can't confirm against impl Trait return types.
Not in the fully general case, but ! does implement Display, so it would work in the case you posted.
Although you said "mode of operation" and I can't get behind that idea, I think the choice to just wrap overflow by default for the integer types in release builds was probably a mistake. It's good that I can turn it off, but it shouldn't have been the default.
[1]: https://downloads.haskell.org/~ghc/7.10.3-rc1/users_guide/ty...
I personally see Rust as an ideal "second system" language, that is, you solve a business case in a more forgiving language first, then switch (parts) to Rust if the case is proven and you need the added performance / reliability.
Also we don't need leftpad like libraries, which is the direction Rust seems to be going when I try some some basic application that has like 50 crate dependencies.
There is nothing in our domain of distributed systems based on SaaS products, mobile OSes, and managed cloud environments, that would profit from a borrow checker.
In Rust lifetimes for references are part of the type, so &'a str and &'b str could be different types, even though they're both string slice references.
Beyond that, Rust tracks two important "thread safety" properties called Sync and Send, and so if your Thing ends up needing to be Send (because another thread gets given this type) but it's not Send, that's a type error just as surely as if it lacked some other needed property needed for whatever you do with the Thing, like it's not totally ordered (Ord) or it can't be turned into an iterator (IntoIterator)
You can even go more crazy with linear types, effects, formal proofs or dependent types.
What Rust has achieved, was definitely make these ideas more mainstream.
Which languages are those?
Caml Light, OCaml, Miranda, Haskell, Coq, Agda, Lean, Scala, Swift, F#, F*, Idris, ATS, and naturally Rust.
They definitely could have made it more ergonomic though. Pin is super confusing and there are a disappointing number of footguns, e.g. it's very easy to mess up loop/select and that's super common.
It's just so brittle. How can anyone think this is a good idea?
It is a tradeoff between making some things easier. And probably compiler is not mature enough to catch this mistake yet but it will be at some point.
Zig being an auteur language is a very good thing from my perspective, for example you get this new IO approach which is amazing and probably wouldn’t happen if Andrew Kelley wasn’t in the position he is in.
I have been using Rust to write storage engines past couple years and it’s async and io systems have many performance mistakes. Whole ecosystem feels like it is basically designed for writing web servers.
An example is a file format library using Io traits everywhere and using buffered versions for w/e reason. Then you get a couple extra memcopy calls that are copying huge buffers. Combined with global allocation everywhere approach, it generates a lot of page faults which tanks performance.
Another example was, file format and just compute libraries using asyncio traits everywhere which forces everything to be send+sync+’static which makes it basically impossible to use in single thread context with local allocators.
Another example is a library using vec everywhere even if they know what size they’ll need and generating memcopies as vec is getting bigger. Language just makes it too easy.
I’m not saying Rust is bad, it is a super productive ecosystem. But it is good that Zig is able to go deeper and enable more things. Which is possible because one guy can just say “I’ll break the entire IO API so I can make it better”.
Obviously nobody knows they've made this mistake, that's why it is important for the compiler to reject the mistake and let you know.
I don't want to use an auteur language, the fact is Andrew is wrong about some things - everybody is, but because it's Andrew's language too bad that's the end of the discussion in Zig.
I like Rust's `break 'label value`. It's very rarely the right thing, but sometimes, just sometimes, it's exactly what you needed and going without is very annoying. However IIUC for some time several key Rust language people hated this language feature, so it was blocked from landing in stable Rust. If Rust was an auteur language, one person's opinion could doom that feature forever.
Here's a page of non-bugs: https://www.reddit.com/r/ProgrammingLanguages/comments/1hd7l...
(To be clear to others, it's not even that this is 100% a bad thing, but people love to shit on "design by committee" so much, it helps to have a bit of the opposite)
What's happening is that compiler knows the two errors come from disjoint error set, but it promotes them both to anyerror
Details at https://github.com/ziglang/zig/issues/25046
Like, how can anyone think that requiring the user to always remember to explicitly write `mutex.unlock()` or `defer mutex.unlock()` instead of just allowing optional explicit unlock and having it automatically unlock when it goes out of scope by default is a good idea? Both Go and Zig have this flaw. Or, how can anyone think that having a cast that can implicitly convert from any numeric type to any other in conjunction with pervasive type inference is a good idea, like Rust's terrible `as` operator? (I once spent a whole day debugging a bug due to this.)
As a side note, I hate the `as` cast in Rust. It's so brittle and dangerous it doesn't even feel like a part of the language. It's like a JavaScript developer snuck in and added it without anyone noticing. I hope they get rid of it in an edition.
345 more comments available on Hacker News