Unexpected productivity boost of Rust
288 comments
·August 27, 2025nneonneo
mmastrac
I've been writing Rust code for a while and generally if it compiles, it works. There are occasional deadlocks and higher-level ordering issues from time-to-time, but modulo bugs, the compiler succeeding generally means it'll run a decent amount of your project.
jabwd
Though my code complexity is far FAR from what you've been writing it has been a similar experience for me. There are a few footguns and a bug in chrono I still have to find the energy to report or fix; which has been causing a bi-yearly issue, but apart from that happy lil programmer.
munchler
> I have found that Rust's strong safety guarantees give me much more confidence when touching the codebase. With that extra confidence I'm much more willing to refactor even critical parts of the app, which has a very positive effect on my productivity, and long-term maintainability.
That's great, but the graph at the top shows your productivity more than doubling as the size of the project increases, which seems very dubious. Perhaps this is just intended as visual hyperbole, but it triggers my BS detector.
merdaverse
Code written below your line gets executed if you don't return early. More breaking news at 8.
Seriously, why would you think that assigning a value would stop your script from executing? Maybe the Typescript example is missing some context, but it seems like such a weird case to present as a "data race".
Arch-TK
Assigning to `window.location.href` has a side effects. The side effect is that your browser will navigate to wherever you assigned, as if you had clicked a link. This is already a surprising behaviour, but given that this assignment is effectively loading a new page in-place, kind of like how `execve` does for a process, I can totally see how someone would think that JS execution would stop immediately after a link is clicked.
It's obviously not a good idea to rely on such assumptions when programming, and when you find yourself having such a hunch, you should generally stop and verify what the specification actually says. But in this case, the behaviour is weird, and all bets are off. I am not at all surprised that someone would fall for this.
stouset
Part of the problem is that we unknowingly make a million little assumptions every day in the course of software development. Many of them are reasonable, some of them are technically unreasonable but fine in practice, and some of them are disasters waiting to happen. And it's genuinely hard to not only know which are which, but to notice even a fraction of them in the first place.
I'm sure I knew the href thing at one point. It's probably even in the documentation. But the API itself leaves a giant hole for this kind of misunderstanding, and it's almost certainly a mistake that a huge number of people have made. The more pieces of documentation we need to keep in our heads in order to avoid daily mistakes, the exponentially more likely it is we're going to make them anyway.
Good software engineering is, IMHO, about making things hard to hold the wrong way. Strong types, pure functions without side effects (when possible), immutable-by-default semantics, and other such practices can go a long way towards forming the basis of software that is hard to misuse.
dkarl
> when you find yourself having such a hunch, you should generally stop and verify what the specification actually says
It greatly heartens me that we've made it to the point where someone writing Javascript for the browser is recommended to consult a spec instead of a matrix of browsers and browser versions.
However, that said, why would a person embark on research instead of making a simple change to the code so that it relies on fewer assumptions, and so that it's readable and understandable by other programmers on their team who don't know the spec by heart?
lights0123
exit(), execve(), and friends do immediately stop execution—I could understand why you'd think a redirect would as well.
JoshTriplett
Exactly. Given that JavaScript runs in the context of a page, redirecting off of the page seems like it should act like a "noreturn" function...but it doesn't. That seems like a very easy mistake to make.
dvt
The redirect is an assignment. In no language has a variable assignment ever stopped execution.
JoshTriplett
$ python3
Python 3.13.7 (main, Aug 20 2025, 22:17:40) [GCC 14.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class MagicRedirect:
... def __setattr__(self, name, value):
... if name == "href":
... print(f"Redirecting to {value}")
... exit()
...
>>> location = MagicRedirect()
>>> location.href = "https://example.org/"
Redirecting to https://example.org/
$ordu
Try this in C:
*(int*)0 = 0;
Modern C compilers could require you to complicate this enough to confuse them, because their approach to UB is weird, if they saw an UB they could do anything. But in olden days such an assignment led consistently to SIGSEGV and a program termination.
dminik
That doesn't seem that obvious to me. You could have a setter that just calls exit and terminates the whole program.
lock1
You could overload operator=() in C++ with a call to exit(), which fulfills "variable assignment that halts the program".
Humphrey
Whether you think that or not is not the issue - the fix is very obvious once pointed out to you. The arguement the author is making is that a bug like that TS issue can be very difficult and time consuming to track down and is not picked up on by the compiler.
love2read
It seems weird to shame someone for talking about their own experience?
drdrey
OP thought the redirect was synchronous, not that it would stop the script from executing
jibal
No, you're mistaken. Read the other comments under the parent. If it were synchronous then it would have stopped the script from executing, much the way POSIX exec() works. If the OP didn't think that the script would stop, then why would he let execution fall through to code that should not execute ... which he fixed by not letting it fall through?
drdrey
I see, thanks
jemiluv8
This is more a control flow issue than a data race issue. I've seen this countless times. And it is often a sign that you don't spend too much time writing JavaScript/Typescript. You get shot in the foot by this very often. And some linters will catch this - most do actyally
IshKebab
Whenever someone talks about a surprising paper cut like this you always see misguided "this is obvious" comments.
No shit. It's obvious because you literally just read a blog post explaining it. The point is if you sprinkle dozens of "obvious" things through a large enough code based, one of them is going to bite you sooner or later.
It's better if the language helps you avoid them.
BinaryIgor
Don't most of the benefits just come down to using a statically typed and thus compiled language? Be it Java, Go or C++; TypeScript is trickier, because it compiles to JavaScript and inherits some issues, but it's still fine.
I know that Rust provides some additional compile-time checks because of its stricter type system, but it doesn't come for free - it's harder to learn and arguably to read
pornel
To a large extent yes, but Rust adds more dimensions to the type system: ownership, shared vs exclusive access, thread safety, mutually-exclusive fields (sum types).
Ownership/borrowing clarifies whether function arguments are given only temporarily to view during the call, or whether they're given to the function to keep and use exclusively. This ensures there won't be any surprise action at distance when the data is mutated, because it's always clear who can do that. In large programs, and when using 3rd party libraries, this is incredibly useful. Compare that to that golang, which has types for slices, but the type system has no opinion on whether data can be appended to a slice or not (what happens depends on capacity at runtime), and you can't lend a slice as a temporary read-only view (without hiding it behind an abstraction that isn't a slice type any more).
Thread safety in the type system reliably catches at compile time a class of data race errors that in other languages could be nearly impossible to find and debug, or at very least would require catching at run time under a sanitizer.
zelphirkalt
What annoys me about borrowing is, that my default mode of operating is to not mutate things if I can avoid it, and I go to some length in avoiding it, but Rust then forces me to copy or clone, to be able to use things, that I won't mutate anyway, after passing them to another procedure. That creates a lot of mental and syntactical overhead. While in an FP language you are passing values and the assumption is already, that you will not mutate things you pass as arguments and as such there is no need to have extra stuff to do, in order to pass things and later still use them.
Basically, I don't need ownership, if I don't mutate things. It would be nice to have ownership as a concept, in case I do decide to mutate things, but it sucks to have to pay attention to it, when I don't mutate and to carry that around all the time in the code.
vlovich123
It sounds like you may not actually know Rust then because non-owning borrow and ownership are directly expressible within the type system:
Non-owning non mutating borrow that doesn’t require you to clone/copy:
fn foo(v: &SomeValue)
Transfer of ownership, no clone/copy needed, non mutating: fn foo(v: SomeValue)
Transfer of ownership, foo can mutate: fn foo(mut v: SomeValue)
AFAIK rust already supports all the different expressivity you’re asking for. But if you need two things to maintain ownership over a value, then you have to clone by definition, wrapping in Rc/Arc as needed if you want a single version of the underlying value. You may need to do more syntax juggling than with F# (I don’t know the language so I can’t speak to it) but that’s a tradeoff of being a system engineering language and targeting a completely different spot on the perf target.pornel
Borrowing isn't for mutability, but for memory management and limiting data access to a static scope. It just happens that there's an easy syntax for borrowing as shared or exclusive at the same time.
Owned objects are exclusively owned by default, but wrapping them in Rc/Arc makes them shared too.
Shared mutable state is the root of all evil. FP languages solve it by banning mutation, but Rust can flip between banning mutation or banning sharing. Mutable objects that aren't shared can't cause unexpected side effects (at least not any more than Rust's shared references).
arnsholt
Ownership serves another important purpose: it determines when a value is freed.
timeon
> While in an FP language you are passing values
By passing values do you mean 'moving'? Like not passing reference?
w10-1
Readers would benefit from distinguishing effects systems from type systems - error handling, async code, ownership, pointers escaping, etc. are all better understood as effects because they pertain to usage of a value/type (though the usage constraints can depend on the type properties).
Similarly, Java sidesteps many of these issues in mostly using reference types, but ends up with a different classes of errors. So the C/pointer family static analysis can be quite distinct from that for JVM languages.
Swift is roughly on par with Rust wrt exclusivity and data-race safety, and is catching up on ownership.
Rust traits and macros are really a distinguishing feature, because they enable programmer-defined constraints (instead of just compiler-defined), which makes the standard library smaller.
ModernMech
Swift has such a long way to go in general ergonomics of its type system, it's so far behind compared to Rust. The fact that the type checker will just churn until it times out and asks the user to refactor so that it can make progress is such a joke to me, I don't understand how they shipped that with a straight face.
timeon
Swift even if catching up a bit is probably not going to impose strict border between safe and unsafe.
saghm
Yes, all four of them will have some checks that won't be present in a dynamic language, but the differences between them are large enough to be significant. Riding a bike and driving a car are both much faster than going on foot, but if you only view this as a "benefit that comes down to using wheels", you're missing some pretty meaningful details.
arwhatever
I might suspect that if you are lumping all statically-typed languages into a single bucket without making particular distinction among them, then you might not have fully internalized the implications of union (aka Rust enum aka sum) typed data structures combined with exhaustive pattern matching.
I like to call it getting "union-pilled" and it's really hard to accept otherwise statically-typed languages once you become familiar.
JoshTriplett
Or the fact that Rust's type system includes things like Send and Sync, which aren't tracked and enforced in many otherwise-statically-typed languages.
C is statically typed, but its type system tracks much less.
1718627440
My interpretation is, that C doesn't have data types, but memory layout types.
b_e_n_t_o_n
Afaik they aren't true unions but sum types, which have different implications.
And fwiw I've used unions in typescript extensively and I'm not convinced that they're a good idea. They give you a certain flexibility to writing code, yes, does that flexibility lead to good design choices, idk.
kibwen
TypeScript unions are very different from Rust enums, and they lead to different design decisions. IMO Rust-style tagged unions are essential in any new language coming out today, it's really a pity that people didn't pick up on this in the 70s.
ModernMech
enums + match expressions + tagged unions are the secret sauce of Rust.
marcosdumay
> Don't most of the benefits just come down to using a statically typed and thus compiled language?
Doesn't have to be compiled to be statically typed... but yeah, probably.
> Be it Java, Go or C++;
Lol! No. All static type systems aren't the same.
TypeScript would be the only one of your examples that brings the same benefit. But the entire system is broken due to infinite JS Wats it has to be compatible with.
> it's harder to learn and arguably to read
It's easier to learn it properly, harder to vibe pushing something into it until it seems to works. Granted, vibe pushing code into seemingly working is a huge part of initial learning to code, so yeah, don't pick Rust as your first language.
It's absolutely not harder to read.
jauntywundrkind
One other major factor I'd throw on the heap: traits / implementation traits. They act as both an interface system and as a sort of Extension Method system (as seen in c#).
But where-as with interfaces, typically they require you early define what your class implements. Rust gives you a late-bound-ish (still compile time but not defined in the original type) / Inversion of Control way to take whatever you've got and define new things for it. In most languages what types a thing has are defined by the library, but Rust not just allows but is built entirely around taking very simple abstract thing and constructing bigger and bigger toolkits of stuff around them. Very Non-zero sum in ways that languages rarely are.
There's a ton of similarity to Extension Methods, where more can get added to the type. But traits / impls are built much more deeply into rust, are how everything works. Extension Methods are also, afaik, just methods, where-as with Rust you really adding new types that an existing defined-elsewhere thing can express.
I find it super shocking (and not because duh) that Rust's borrow checking gets all the focus. Because the type system is such a refreshing open ended late-defined reversal of type system dogma, of defining everything ahead of time. It seems like such a superpower of Rust that you can keep adding typiness to a thing, keep expanding what a thing can do. The inversion here is, imo, one of the real largely unseen sources of glory for why Rust keeps succeeding: you don't need to fully consider the entire type system of your program ahead of time, you can layer in typing onto existing types as you please, as fits, as makes sense, and that is a far more dynamic static type system than the same old highly constrained static type dreck we've suffered for decades. Massive break forward: static, but still rather dynamic (at compile time).
lmm
> Don't most of the benefits just come down to using a statically typed and thus compiled language? Be it Java, Go or C++; TypeScript is trickier, because it compiles to JavaScript and inherits some issues, but it's still fine.
No. You have to have a certain amount of basic functionality in your type system; in particular, sum types, which surprisingly many languages still lack.
(Note that static typing does not require compilation or vice versa)
> I know that Rust provides some additional compile-time checks because of its stricter type system, but it doesn't come for free - it's harder to learn and arguably to read
ML-family languages are generally easier to learn and read if you start from them. It's just familiarity.
stocksinsmocks
Yes, but more importantly writing a program that compiles in Rust guarantees you a front page spot on HN.
rvz
> Don't most of the benefits just come down to using a statically typed and thus compiled language? Be it Java, Go or C++; TypeScript is trickier, because it compiles to JavaScript and inherits some issues, but it's still fine.
Yes. The type systems of these modern compiled languages are more sound than anything that Javascript and Typescript can ever provide.
Anyone using such languages that have a totally weak type system and a dynamic typing system as well is going to run into hundreds of headaches - hence why they love properly typed-systems such as Rust which actually is a well designed language.
raphinou
Though it's not the only benefit, I enjoy rust and fsharp's typesystems most when refactoring code. Fearless refactoring is the right expression here.
estebank
The only issue with it is that Rust's aversion to half-baked code means that you can't have "partially working code" during the refactor: you either finish it or bail on it, without the possibility to have inconsistent codebase state. This is particularly annoying for exploratory code.
On the other hand, that strictness is precisely what leads people to end up with generally reasonable code.
tialaramex
I find a healthy dose of todo!() is excellent for this.
match foo {
(3...=5, x, BLABLABLA) => easy(x),
_ => todo!("I should actually implement this for non-trivial cases"),
}
The nice thing about todo!() is that it type checks, obviously it always diverges so the type match is trivial, but it means this compiles and, so long as we don't cause the non-trivial case to happen, it even works at runtime.estebank
The thing is I want an equivalent to `todo!()` for the type-system. A mode of operation where if you have some compile errors, you can still run tests. Like for example, if you have
fn foo() -> impl Display {
NotDisplay::new()
}
and a test references `foo`, then it gets replaced for the purposes of the test with fn foo() -> impl Display {
panic!("`NotDisplay` doesn't `impl Display`")
}
This should not be part of the language, but part of the toolchain.Same thing for the borrow-checker.
Cthulhu_
It's a tradeoff, reminds me of Go not compiling if you have an unused variable; the strictness is a feature and basically locks out sloppy / half baked code.
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.
veber-alex
I find the Zig example to be shocking.
It's just so brittle. How can anyone think this is a good idea?
tialaramex
I assume it's a bug. However because this is an auteur language if you want the bug fixed it will be important to ensure the auteur also thinks it is a bug. If they get it into their head that it's supposed to be like this, they'll double down and regardless of how many people are annoyed they're insist on it.
ozgrakkurt
Tbh you would just use ErrorTypeName.ErrorKind to check equality and not error.ErrorKind if you are worried about this.
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”.
tialaramex
> if you are worried about this
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.
veber-alex
Knowing how the Zig developers operate it's 100% not a bug and it's exactly the case you described.
jibal
Why would you assume that? It's very intentional design decision.
geysersam
Can't a linter catch that you're referring to an error that doesn't exist anywhere else in your system and warn you about that and suggest you use switch instead of if?
zparky
I didn't even see the error after the first glance until I read your comment.
WD-42
I saw this comment first and then read the zig and still couldn’t find the error until I read the explanation below it. This has to be a bug.
veber-alex
Yeah, I had to do a double take while reading the article because I missed the error completely while looking at the code.
kouteiheika
Every language has questionable design decisions that lead to brittle code, although some more than others.
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.)
veber-alex
You are right, but it doesn't mean we can't complain about it :)
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.
JoshTriplett
> 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.
Rust language hat on: I hope so too. We very much want to, once we've replaced its various use cases.
jcranmer
As much as I hate 'as' and try to avoid it, it also covers several things that are impossible otherwise (integer <-> float casts are impossible without it, e.g.). I've found that sometimes just being able to express a coerce-to-type-damn-the-consequences is useful.
Another painful bugbear is when I'm converting to/from usize and I know that it is really either going to be a u64 or maybe u32 in a few cases, and I don't care about breaking usize=u128 or usize=u16 code. Give me a way to say that u32 is Into<usize> for my code!
Starlevel004
As someone working with bitfields and enums-to-ints a lot, you can take ``as`` out of my cold dead hands.
Phil_Latio
I would've assumed the error set is generated based on function signatures. Sick stuff.
neandrake
Rust caught the lock being held across an await boundary, but without further context I'd hedge there's still a concurrency issue there if the solution was to release the lock before the await.
Presumably the lock is intended to be used for blocking until the commit is created, which would only be guaranteed after the await. Releasing the lock after submitting the transaction to the database but before getting confirmation that it completed successfully would probably result in further edge cases. I'm unfamiliar with rust's async, but is there a join/select that should be used to block, after which the lock should be unlocked?
lilyball
If you need to hold a lock across an await point, you can switch to an async-aware mutex. Both the futures crate and the tokio crate have implementations of async-aware mutexes. You usually only want this if you're holding it across an await point because they're more expensive than blocking mutexes (the other reason to use this is if you expect the mutex to be held for a significant amount of time, so an async-aware lock will allow other tasks to progress while waiting to take the lock).
Cyph0n
You can use a async-aware mutex if you require it to be held across await points. For example, if using the Tokio runtime: https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html.
aeve890
>The code will compile just fine. The Zig compiler will generate a new number for each unique 'error.*'
This is wild. I assume there's at least the tooling to catch this kind of errors right?
TUSF
There's no special tooling to catch this, because nobody catches an error with if-else—it's simply not idiomatic. Everyone uses switch statements in the catch block, and I've never seen anybody using anything other than switch, when catching an error.
dminik
Both the Zig standard library as well as several third party projects do check errors like this.
I already commented on Zig compiler/stdlib code itself, but here's Tigerbeetle and Bun, the two biggest(?) Zig codebases:
https://github.com/search?q=repo%3Atigerbeetle%2Ftigerbeetle...
https://github.com/search?q=repo%3Aoven-sh%2Fbun%20%22%3D%3D...
dminik
Ok, while it's cool that the TigerBeetle link now shows no matches (down from two) the comment now feels wrong. Anyways, you guys left in the `!= error.` checks, so here's some snapshots that hopefully won't be invalidated :P
https://github.com/tigerbeetle/tigerbeetle/blob/b173fdc82700...
https://github.com/tigerbeetle/tigerbeetle/blob/b173fdc82700... (different file, same check.)
veber-alex
But why?
If I just need to check for 1 specific error and do something why do I need a switch?
In Rust you have both "match" (like switch) and "if let" which just pattern matches one variant but both are properly checked by the compiler to have only valid values.
arwalk
The real problem is not about the if-else, its that he's comparing to the global error set, and not to the FileError error set he created specifically to define AccessDenied.
love2read
The real problem is that this compiles without error
love2read
“There’s no specific tooling to catch this, because nobody[…]”. So? This is a standard library/language feature, which is usually the first place people go for features in the language. To say that nobody uses it is stupid.
arwalk
The example is a bit dubious. Sure, it compiles just fine, because the author is not using errors properly in zig. Here, he uses the global error set with `error. AccessDenid`, and as stated, it compiles just fine because when you reach the global error set, it's integers all the way down.
If the author had written `FileError.AccessDenid`, this would not have compiled, as it would be comparing with the `FileError` error set.
The global error set is pretty much never used, except when you want to allow a user to provide his own errors, so you allow the method to return `anyerror`.
dminik
You say never, but even the Zig stdlib does this occasionally.
Like here in `std/tar.zig`: https://github.com/ziglang/zig/blob/50edad37ba745502174e49af...
Or here in `main.zig`: https://github.com/ziglang/zig/blob/50edad37ba745502174e49af...
And in a bunch of other places: https://github.com/search?q=repo%3Aziglang%2Fzig+%22%3D%3D+e...
veber-alex
Why does the compiler decay the FileError and the global error set to an integer? If they were unique types, the if statement would not have compiled.
arwalk
`FileError.AccessDenied` is a unique value in a unique error set. `error.AccessDenid` has not been defined anywhere and hence is just given an integer value at some point by the compiler.
As I stated before, this error wouldn't even exist in the first place in no codebase ever: look how the method that fails returns a `FileError` and not an `anyerror`
It could be rightly argued that it still shouldn't compile though.
empath75
All examples of this type come down to "the user made a mistake", but that is kind of the the entire point. It is not possible to make that mistake in rust.
arwalk
I'm not saying that zig has the same level of safety than rust, i'm just saying that grabbing a knife by the blade is not an argument for using a spoon.
The error presented in this example would not be written by any zig developer. Heck, before this example i didn't even knew that you could compare directly to the global error set, and i maintain a small library.
zig and rust do not have the same scope. I honestly do not think they should be compared. Zig is better compared to C, and rust is better compared to C++.
jaccola
I would love to see a web app with an interface where I select two programming languages and it shows me a really clean snippet of code in language A and says "Look how terribly hard and unclean this is to achieve in language B". (and then you could reverse to see where B outshines A).
Languages are a collection of tradeoffs so I'm pretty sure you could find examples for every two languages in existence. It also makes these kinds of comparisons ~useless.
prerok
For one, snippets is exactly where you don't get good language comparisons.
For example, python is AFAIK the lead language to get something done quickly. At least as per AoC leaderboards. It's a horrible language to have in production though (experienced it with 700k+ LOC).
Rust is also ok to do for AoC, but you will (based on stats I saw) need about 2x time to implement. Which in production software is definitely worth it, because of less cost in fixing stupid mistakes, but a code snippet will not show you that.
guywithabike
Rosetta Code is what you're looking for, I believe: https://rosettacode.org/
jibal
Actually, these sorts of comparisons are extremely useful, and an argument based on imagining an unknown case that makes the argument work is fallacious.
suriya-ganesh
I vibe with this entirely.
Rust to me makes a lot more sense. The compiler gives reasonable errors, the code structure is clean and it's obvious where everything should go.
I just can't deal with Typescript at all. There is a sense of uncertainty in TypeScript that is just unbearable. It is really hard to be confident about the code.
koakuma-chan
I encourage every one to at least stop writing code in Python.
nilslindemann
People who recommend that other people stop using one of the best documented languages on the planet with a huge library ecosystem, a friendly user base, a clean syntax, excellent reference documentation, intuitive function names, readable tracebacks, superb editor and AI support.
ironmagma
Intuitive function names like __new__() and __init__()? Or id() and pickle.dumps()?
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).
jvanderbot
The benefits realized can be mostly attributed to strong type checking.
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.
koakuma-chan
Do you know a language other than Rust that has alternative for docs.rs? In JavaScript and Python they never bother to have any documentation or reference, one notable example that gets me frustrated these days is the openai SDK for TypeScript. There is no documentation for it. I have to go look at the source code to figure out what the hell goes on.
cbm-vic-20
javadoc.io is similar to (but not as good as) docs.rs for the Java ecosystem. It pulls the JavaDoc out of packages that are published to the Maven Central repository. It doesn't have good discoverability like docs.rs, though, and it's dependent on the publishers actually including the javadoc files.
9question1
These days isn't the solution to this just "ask <insert LLM of choice here>" to read the code and write the documentation"?
veber-alex
Here is some actual useful advice:
Use a type checker! Pyright can get you like 80% of Rust's type safety.
deathanatos
I've not tried Pyright, but mypy on any realistic, real-world codebase I've thrown at it emits ~80k errors. It's hard to get started with that.
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:
from typing import Dict
JsonValue = str | Dict[str, "JsonValue"]
def foo() -> JsonValue:
x: Dict[str, str] = {"a": "b"}
return x
x: JsonValue = foo()
That will get you: example.py:7: error: Incompatible return value type (got "dict[str, str]", expected "str | dict[str, JsonValue]") [return-value]FreakLegion
Everyone stubs their toe on container invariance once, then figures it out and moves on. It's not unique to Python and developers should understand the nuances of variance.
veber-alex
I don't use mypy so I can't comment on it but at least from what I have seen pyright is deterministic in it's output and get output json.
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...
zelphirkalt
I used mypy just fine for a previous job. If you are getting 80k errors, that means you are either starting very late to use the type checker and have done many dubious things before, or you didn't exclude your venv from being type checked by mypy.
koakuma-chan
> There is an extension to aid in this, but it regrettably SIGSEGVs.
Love this.
jaza
Exactly! I code in Python every day, I haven't done so without a type checker (usually mypy) for years, I don't push a single change without type-checking it first, it catches stupid mistakes and saves me from breaking production all the time.
koakuma-chan
I don't agree that Python can be saved by a type checker. The language itself is flawed irreversibly, as well as its ecosystem. It's a big big mess. Can't build reliable software in Python.
grep_it
Is this rage-bait? A language alone doesn't dictate reliability. There are tons of large scale systems out there running on Python. As for the language being, "flawed irreversibly", I'd be curious to hear you expand on that with examples.
dkdcio
you can and this is a juvenile position. there is reliable software in any language as popular and widespread as Python. every language is flawed
null
ninetyninenine
It can't be 100% saved, but like the OP said it's 80% saved.
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:
A. You claim python can't build reliable software.
B. Reliable Software for python actually exists, therefore your claim is incorrect
C. You therefore must not have experience with building any software with python and must have your hand held and be baby-sitted by rusts type checker.
Just spitting facts.munificent
> 80% of Rust's type safety.
Sort of like closing 80% of a submarine's hatches and then diving.
kragen
What are the major pros and cons of Pyright and Mypy relative to one another?
guitarbill
Sorry, it's not even close to Rust. Or even C# or Java. It can't provide the same "fearless refactoring". It is better than being blind/having to infer everything manually. That's not saying much.
And that's assuming the codebase and all dependencies have correct type annotations.
lynndotpy
Pythons power is in the REPL. It's good for when you want to do something ad-hoc that's too much for Bash or a graphing calculator. It has a large enough ecosystem that you can make a PoC for basically anything in a day.
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.)
love2read
the problem is that when you write enough <1kloc projects, a couple of them are useful enough to stay used in which case you are now maintaining python in prod.
hnaccount_rng
Yes but the alternative is "not having those projects at all" not "having them in a 'better' language"
lynndotpy
I don't think that's inevitable, you can just not do that. I've been doing this for 15 years and it hasn't happened to me.
marcosdumay
On that graph, both curves should go monotonically down, just one faster than the other.
Last year I ported a virtio-host network driver written in Rust, entirely changing its backend, interrupt mechanism and converting it from a library to a standalone process. This is a complex program that handles low-level memory mappings, VM interrupts, network sockets, multithreading, etc.
What’s remarkable is that (a) I have very little Rust experience overall (mostly a Python programmer), (b) very little virtio experience, and (c) essentially no experience working with any of the libraries involved. Yet, I pulled off the refactor inside of a week, because by the time the project actually compiled, it worked perfectly (with one minor Drop-related bug that was easily found and fixed).
This was aided by libraries that went out of their way to make sure you couldn’t hold them wrong, and it shows.