Skip to content(if available)orjump to list(if available)

A Rust shaped hole

A Rust shaped hole

111 comments

·July 15, 2025

Expurple

> If someone posts a patch or submits a PR to a codebase written in C, it is easier to review than any other mainstream language. There is no spooky at a distance. [..] Changes are local.

Lol, wut? What about about race conditions, null pointers indirectly propagated into functions that don't expect null, aliased pointers indirectly propagated into `restrict` functions, and the other non-local UB causes? Sadly, C's explicit control flow isn't enough to actually enable local reasoning in the way that Rust (and some other functional languages) do.

I agree that Go is decent at this. But it's still not perfect, due to "downcast from interface{}", implicit nullability, and similar fragile runtime business.

I largely agree with the rest of the post! Although Rust enables better local reasoning, it definitely has more complexity and a steeper learning curve. I don't need its manual memory management most of the time, either.

Related post about a "higer-level Rust" with less memory management: https://without.boats/blog/notes-on-a-smaller-rust/

uecker

It is a bit unclear to me why somebody who rejects C++ because "I once spent an entire year in the heaven of C++, walking around in a glorious daze of std::vector and RAII, before one day snapping out of it and realizing that I was just spawning complexity that is unrelated to the problem at hand." (which I can absolutely agree with!) is picking Rust from all options. If there is a language that can rival C++ in terms of complexity, it is Rust.

tialaramex

I've seen this idea from a few people and I don't get it at all.

Rust is certainly not the simplest language you'll run into, but C++ is incredibly baroque, they're not really comparable on this axis.

One difference which is already important and I think will grow only more important over time is that Rust's Editions give it permission to go back and fix things, so it does - where in C++ it's like venturing into a hoarder's home when you trip over things which are abandoned in favour of a newer shinier alternative.

rich_sasha

C++'s complexity is coming from how eager it is to let you shoot yourself in the foot. Rust will make you sweat blood to prove the bird in the sky you're shooting at is really not your own foot.

bcrl

And the gun isn't a gun, it's just a bunch of rope that you somehow got tangled into an explosive with a detonator you can't quite seem to locate.

eldenring

It's really not that bad.

null

[deleted]

bryanlarsen

Reading between the lines, the author is a Haskell fan. Haskell is another "complicated" language, but the complexity feels much different than the C++ complexity. Perhaps I would describe it as "complexity that improves expressiveness". If you like Haskell for its expressiveness but dislike C++ for it's complexity, I suspect Rust is a language you're going to like.

tines

The two are incomparable in both quality and quantity. The complexity of Rust comes from the fact that it's solving complex problems. The complexity of C++ comes from a poorly thought out design and backwards-compatibility. (Not to slight the standards committee; they are smart people, and did the best with what they had.)

Anothere way of putting it is, if you didn't care about backwards-compatibility, you could greatly simplify C++ without losing anything. You can't say the same about Rust; the complexity of Rust is high-entropy, C++'s is low-entropy.

materielle

The C++ standard committee is definitely smart. But language design requires sense beyond just being smart.

They didn’t do the best with what they had. Sure, some problems were caused by C backwards compatibility.

But so much of the complexity and silliness of the language was invented by the committee themselves.

uecker

I would say that most of the complexity in both cases comes from overengineering.

tines

Can you give some examples of Rust and C++ design that are over engineered?

dijit

You’re right that Rust is a ball of complication.

I am a fan of Rust but it’s definitely a terse language.

However there are definitely signs that they have thought about making it as readable as possible (by omitting implicit things unless they’re overwritten, like lifetimes).

I’m reminded also about a passage in a programming book I once read about “the right level of abstraction”. The best level of abstraction is the one that cuts to the meat of your problem the quickest - spending a significant amount of time rebuilding the same abstractions over and over (which, is unfortunately often the case in C/C++) is not actually more simple, even if the language specifications themselves are simpler.

C codebases in particular, to me, are nearly inscrutable unless I spend a good amount of time unpicking the layers of abstractions that people need to write to make something functional.

I still agree that Rust is a complex language, but I think that largely just means it’s frontloading. a lot of the understanding about certain abstractions.

uecker

I do not really agree with respect to C. I often have to deal with C code written by unexperienced programmers. It is always relatively easy to refactor it step by step. For C++ this is much more painful because all the tools invented to make the code look concise make it extremely hard to follow and change. From the feature set, I would say that Rust is the same (but I have no experience refactoring Rust code).

AstralStorm

In personal experience, the much stronger and composite type model in Rust makes it easier to refactor.

Adding features in particular is a breeze and automatically the compiler/language will track for you the places that use only old set of traits.

Tooling is still newer though and needs polish. Generic handling is interesting at times and there are related missing features for that in the language, vis a vis specializations in particular.

Basic concurrency handling is also quite different in Rust than other languages, but thus usually safer.

bryanlarsen

> but I have no experience refactoring Rust code

But you're willing to write many comments complaining that Rust is hard to refactor. Rust is the easiest language to refactor with I've ever worked in, and I've used a couple dozen or so. When you want to change something, you change it, and then fix compiler errors until it stops complaining. Then you run it, and it works the first time you run it. It's an incredible experience to worry so little about unknown side-effects.

wisty

I'm not at all fluent at Rust, but I think c++ is not just complex, but every c++ project is complete in a different way.

Aeolun

Rust is a lot better at producing helpful error messages than any C++ I’ve seen.

andrepd

It's really not even remotely the same. C++ has literally >50 pages of specification on the topic of initialising values. All of these are inconsistent, not subject to any overarching or unifying rule, and you have to keep it all in mind to not run into bugs or problematic performance.

Rust is a dead simple language in comparison.

jplusequalt

>If there is a language that can rival C++ in terms of complexity

Fair, but this relative. C++ has 50 years of baggage it needs to support--and IMO the real complexity of C++ isn't the language, it's the ecosystem around it.

lmm

If they're serious about their criteria they should go with OCaml (or maybe, like, Swift, or any of dozens of languages in that space).

(Of course they actually do want Haskell but they probably need to get there gradually)

zem

right! the table at the end just screamed "use ocaml and be happy"

legobmw99

nobody gives OCaml a thought in these discussions. It’s such a wonderful language!

tux3

>To paraphrase Norvig's Latency numbers a programmer should know, if we imagine a computer that executes 1 CPU instruction every second, it would take it days to read from RAM.

It's a detail, but this is a little bit off. RAM latency is roughly around ~100ns, CPUs average a couple instructions per cycle and a few cycles per ns.

Then in the analogy, a stall on RAM is about a 10 minute wait; not quite as bad as losing entire days.

jlokier

In current machines, that's way off depending on how you choose to count "1 CPU instruction" for the metaphor.

Take Apple's latest laptops. They have 16 CPU cores, 12 of those clocking at 4.5 GHz and able to decode/dispath up to 10 instructions per cycle. 4 of those clocking at 2.6 GHz, I'm not sure about their decode/dispatch width but let's assume 10. Those decoder widths don't translate to that many instructions-per-cycle in practice, but let's roll with it because the order of magnitude is close enough.

If the instructions are just right, that's 824 instructions per nanosecond. Or, roughly a million times faster than the 6502 in the Apple-II! Computers really have got faster, and we haven't even counted all the cores yet.

Scaling those to one per second, a RAM fetch taking 100ns would scale to 82400 seconds, which 22.8 hours, just short of a day.

Fine, but we forgot about the 40 GPU cores and the 16 ANE cores! More instructions per ns!

Now we're definitely into "days".

For the purpose of the metaphor, perhaps we should also count the multiple lanes of each vector instruction on the CPU, and lanes on the GPU cores, as if thery were separate processing instructions.

One way to measure that, which seems fair and useful to me, is to look at TOPS instead - tera operations per second. How many floating-point calculations can the processor complex do per second? I wasn't able to find good figures for the Apple M4 Max as a whole, only the ANE component, for which 38 TOPS is claimed. For various reasons tt's reasonable to estimate the GPU is the same order of magnitude in TOPS on those chips.

If you count 38 TOPS as equivalent to "CPU instructions" in the metaphor, then scale those to 1 per second, a RAM fetch taking 100ns scales to a whopping 43.9 days on a current laptop!

tux3

If you're counting all instruction executing in parallel with the maximum on-paper IPC on all CPUs, accelerators, and GPUs, the number your get has no clear relation to RAM latency. It really is comparing apples and oranges.

This scenario where all your 16 cores are doing 10 instructions per clock assumes everything is running without waiting, at full instruction-level and CPU-level parallelism. It's a measure of the maximum paper throughput when you're not blocked waiting on memory.

You could compare that to the maximum throughput of the RAM and the memory subsystem, and that would give you meaningful numbers (for instance, how many bytes/cycle can my cores handle? How many GB/s can my whole system process?).

Trying to add up the combined throughput of everything you can on one side and the latency of a single fetch on the other side will give you a really big number, but as a metaphor it will be more confusing than anything.

renewiltord

This seems like the classic 9 women making a baby in 1 month. Even if the CPU can execute 824 instructions per nanosecond, it can't execute 1 instruction in 1/824 nanoseconds. You can't mix throughput and latency like that.

genshii

This hits close to home. TypeScript is also my language of choice for 90% of the software I write. I agree with the author that TypeScript is very close to the perfect level of abstraction, and I haven't seen another language with a type system that's nearly as enjoyable to use. Of course, TS (any by extension JS) obviously has its issues/complications. Bun solves a lot of the runtime-related issues/annoyances though.

For the other 10% software that is performance-sensitive or where I need to ship some binary, I haven't found a language that I'm "happy" with. Just like the author talks about, I basically bounce between Go and Rust depending on what it is. Go is too simple almost to a fault (give me type unions please). Rust is too expressive; I find myself debugging my knowledge of Rust rather than the program (also I think metaprogramming/macros are a mistake).

I think there's space in the programming language world for a slightly higher level Go-like language with more expressiveness.

daxfohl

I'm surprised ocaml doesn't have more market share here. Native, fast, robust type system, GC, less special syntax than rust, less obtuse than Haskell.

pragmatic

No libraries.

All the niche languages have a chicken and egg problem.

Only way around that is to be able to piggy back on C or JavaScript or Java.

daxfohl

Yeah, though when I say that I mean more like I'm surprised the ecosystem itself hasn't matured more. Rust and Go have both built solid ecosystems up from scratch, but it's a shame that OCaml hasn't since it's a nice middle ground between the two.

ashishb

TypeScript is good as a language. You can't generate static binaries out of it (except Docker images) and that itself is a deal breaker.

genshii

Yeah, that's definitely a huge drawback. Bun lets you get pretty close though with the `--compile` flag: https://bun.sh/docs/bundler

Too bad the binaries are 60MB at a minimum :(

wk_end

I'd love to see someone develop a compiler for TypeScript that got, say, Ocaml-like performance. There's a bunch of reasons why that'd be tough though - you'd probably want a language very-similar-to-but-not-quite-like-TypeScript.

williamstein

This is probably the closest thing to that: https://www.assemblyscript.org/

ellg

its called C#

Tade0

I mean you can, they're just inappropriately large.

Sophistifunk

I very much enjoy reading and writing TS code. What I don't enjoy is the npm ecosystem (and accompanying mindset), and what I can't stand is trying to configure the damn thing. I've been doing this since TSC was first released, and just the other day I wasted hours trying to make a simple ts-node command line program work with file-extension-free imports and no weird disagreements between the ts-node runner and the language server used by the editor.

And then gave up in disgust.

Look, I'm no genius, not by a long shot. But I am both competent and experienced. If I can't make these things work just by messing with it and googling around, it's too damned hard.

renewiltord

I recently came to a production Typescript codebase and it took minutes to compile. Strangely, it could not behave correctly without a linter rule `no-floating-promises` but the linter also took minutes to lint the codebase. It was an astounding exercise in patience. Faster linters like oxlint exist but they don't have a notion of cross-file types so `no-floating-promises` is impossible on them.

The worst part is that `no-floating-promises` is strange. Without it, Knex (some ORM toolkit in this codebase) can crash (segfault equivalent) the entire runtime on a codebase that compiles. With it, Knex's query builders will fail the lint.

It was confusing. The type system was sophisticated enough that I could generate a CamelCaseToSnakeCase<T> type but somehow too weak to ensure object borrow semantics. Programmers on the codebase would frequently forget to use `await` on something causing a later hidden crash until I added the `no-floating-promises` lint, at which point they had to suppress it on all their query builders.

One could argue that they should just have been writing SQL queries and I did, but it didn't take. So the entire experience was fairly nightmarish.

jplusequalt

>There is an apocryphal story about Euler in elementary school solving all the math problems that the teacher gave to the class in a jiffy, so the teacher tells him to sum up the numbers to a thousand to get him to stop pestering for more. The expectation was that Euler would go through the numbers "imperatively", like C, summing them up. Instead, what Euler did was discover the summation formula and solved it "declaratively" like Haskell, in one go, as an equation.

I've heard this story be accounted to Gauss, not Euler.

tines

Very cool, I wish the author good luck! I've been writing a compiler in Rust for a few months now and I absolutely love it. The ways it solves most of the problems it addresses feel like the "right" way to do things.

There are some things that feel a little weird, like the fact that often when you want a more complex data structure you end up putting everything in a flat array/map and using indices as pointers. But I think I've gotten used to them, and I've come up with a few tricks to make it better (like creating a separate integer type for each "pointer" type I use, so that I can't accidentally index an object array with the wrong kind of index).

Rust is one of those languages that change how you think, like Haskell or Lisp or Forth. It won't be easy, but it's worth it.

ashishb

I actively seek out tools written in static languages. They are less fragile and have a longer shelf life.

https://ashishb.net/programming/maintaining-android-app/

Expurple

I agree so hard. That's why I use Hugo for my website. Speed was always only a bonus

Sophistifunk

Sounds like a Zig-shaped hole to me ;-)

spooneybarger

I've written a ton of C in my life and a C lot of Go and I was rofl at the "no spooky action at distance" lines.

This was brilliant performance art. Bless your heart Dear Author, I adore you.

blamestross

Everyone who has said the phrase "Spooky action at a distance" has been proven wrong :)

slowcache

Odin has been really growing on me lately as a language that checks all of those boxes. String types, first class allocators, built in tests, a batteries included philosophy, and ease of use are some of the things that really drew me towards it.

I really wanted to like rust and I wrote a few different small toy projects in it. At some point knowledge of the language becomes a blocker rather than knowledge the problem space, but this is a skill issue that I'm sure would lessen the more I used it.

What really set me off was how every project turned into a grocery list of crates that you need to pull in in order to do anything. It started to feel embarrassing to say that I was doing systems programming when any topic I would google in rust would lead me to a stack overflow saying to install a crate and use that. There seemed to be an anti-DIY approach in the community that finally drew me away.

klntsky

What's the difference between anti-DIY and "batteries included"?