Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> In Rust, creating a mutable global variable is so hard that there are long forum discussions on how to do it. In Zig, you can just create one, no problem.

Well, no, creating a mutable global variable is trivial in Rust, it just requires either `unsafe` or using a smart pointer that provides synchronization. That's because Rust programs are re-entrant by default, because Rust provides compile-time thread-safety. If you don't care about statically-enforced thread-safety, then it's as easy in Rust as it is in Zig or C. The difference is that, unlike Zig or C, Rust gives you the tools to enforce more guarantees about your code's possible runtime behavior.





After using Rust for many years now, I feel that a mutable global variable is the perfect example of a "you were so busy figuring out whether you could, you never stopped to consider whether you should".

Moving back to a language that does this kind of thing all the time now, it seems like insanity to me wrt safety in execution


Global mutable state is like a rite of passage for devs.

Novices start slapping global variables everywhere because it makes things easy and it works, until it doesn't and some behaviour breaks because... I don't even know what broke it.

On a smaller scale, mutable date handling libraries also provide some memorable WTF debugging moments until one learns (hopefully) that adding 10 days to a date should probably return a new date instance in most cases.


Hey, don't tell that to front-end developers, we like our global stores accessible all over.

It's because of the "status quo". Once you start using immutable-first language on the front-end, e.g., Clojurescript - your perspective changes.

I know. I'm mostly gone from FE, the amount of cargo culting and “we do things that way because that's how it's always been” is toxic.

it's the classic Rich hickey talk. simple made easy.

> [...] is trivial in Rust [...] it just requires [...]

This is a tombstone-quality statement. It's the same framing people tossed around about C++ and Perl and Haskell (also Prolog back in the day). And it's true, insofar as it goes. But languages where "trivial" things "just require" rapidly become "not so trivial" in the aggregate. And Rust has jumped that particular shark. It will never be trivial, period.


> languages where "trivial" things "just require" rapidly become "not so trivial" in the aggregate

Sure. And in C and Zig, it's "trivial" to make a global mutable variable, it "just requires" you to flawlessly uphold memory access invariants manually across all possible concurrent states of your program.

Stop beating around the bush. Rust is just easier than nearly any other language for writing concurrent programs, and it's not even close (though obligatory shout out to Erlang).


This is a miscommunication between the values of “shipping” which optimizes for fastest time to delivery and “correctness” which optimizes for the quality of the code.

Rust makes it easy to write correct software quickly, but it’s slower for writing incorrect software that still works for an MVP. You can get away with writing incorrect concurrent programs in other languages… for a while. And sometimes that’s what business requires.

I actually wish “rewrite in Rust” was a more significant target in the Rust space. Acknowledging that while Rust is not great for prototyping, the correctness/performance advantages it provides justifies a rewrite for the long-term maintenance of software—provided that the tools exist to ease that migration.


Lately rust is my primary language, and I couldn't agree more with this.

I've taken to using typescript for prototyping - since its fast (enough), and its trivial to run both on the server (via bun) or in a browser. The type system is similar enough to rust that swapping back and forth is pretty easy. And there's a great package ecosystem.

I'll get something working, iterate on the design, maybe go through a few rewrites and when I'm happy enough with the network protocol / UI / data layout, pull out rust, port everything across and optimize.

Its easier than you think to port code like this. Our intuition is all messed up when it comes to moving code between languages because we look at a big project and think of how long it took to write that in the first place. But rewriting code from imperative language A to B is a relatively mechanical process. Its much faster than you think. I'm surprised it doesn't happen more often.


I'm in a similar place, but my stack is Python->Go

With Python I can easily iterate on solutions, observe them as they change, use the REPL to debug things and in general just write bad code just to get it working. I do try to add type annotations etc and not go full "yolo Javascript everything is an object" -style :)

But in the end running Python code on someone else's computer is a pain in the ass, so when I'm done I usually use an LLM to rewrite the whole thing in Go, which in most cases gives me a nice speedup and more importantly I get a single executable I can just copy around and run.

In a few cases the solution requires a Python library that doesn't have a Go equivalent I just stick with the Python one and shove it in a container or something for distribution.


I'm in the camp of "If your target is Go, then prototype in Go." I don't bother with the intermediate step. Go is already so very close to being a dynamic language that I don't get the point. Just write "bad" Go to prototype quickly. Skip the error checks. Panic for fun. Write long functions. Make giant structs. Don't worry about memory.

You mentioned running someone else's python is painful, and it most certainly is. No other language have I dealt with more of the "Well, it works on my machine" excuse, after being passed done the world's worst code from a "data scientist". Then the "well, use virtual environments"... Oh, you didn't provide that. What version are you using? What libraries did you manually copy into your project? I abhor the language/runtime. Since most of us don't work in isolation, I find the intermediate prototype in another language for Go a waste of time and resources.

Now... I do support an argument for "we prototype in X because we do not run X in production". That means that prototype code will not be part of our releases. Let someone iterate quickly in a sandbox, but they can't copy/paste that stuff into the main product.

Just a stupid rant. Sorry. I'm unemployed. Career is dead. So, I shouldn't even hit "reply"... but I will.


I second your experience with Python. I've been coding in Python for 10+ years. When I get passed down some 'data scientist' code, I often it breaks.

With Rust, it was amazing - it was a pain to get it compiled and get past the restrictions (coming from a Python coder) - the code just ran without a hitch, and it was fast, never even tried to optimize it.

As a Python 'old-timer' , I also am not impressed with all the gratuitous fake typing , and especially Pydantic. Pydantic feels so un-pythonic, they're trying to make it like Go or Rust, but its falling flat, at least for me.


Is there a good resource on how to get better at python prototyping?

The typing system makes it somewhat slow for me and I am faster prototyping in Go then in Python, despite that I am writing more Python code. And yes I use type annotations everywhere, ideally even using pydantic.

I tend to use it a lot for data analytics and exploration but I do this now in nushell which holds up very well for this kind of tasks.


Just do it I guess? :D

When I'm receiving some random JSON from an API, it's so much easier to drop into a Python REPL and just wander around the structure and figure out what's where. I don't need to have a defined struct with annotations for the data to parse it like in Go.

In the first phase I don't bother with any linters or type annotations, I just need the skeleton of something that works end to end. A proof of concept if you will.

Then it's just iterating with Python, figuring out what comes in and what goes out and finalising the format.


Thank you, but the JSON API stuff is exactly what i am using nushell for at the moment. Makes it trivial to navigate large datasets.

For me it's pretty hard to work without type annotations, it just slows me down.

Don't get me wrong, I really like python for what it is, I simply missing out on the fast prototype stuff that everyone else is capable of.


> Rust makes it easy to write correct software quickly, but it’s slower for writing incorrect software that still works for an MVP.

I don't find that to be the case. It may be slower for a month or two while you learn how to work with the borrow checker, but after the adjustment period, the ideas flow just as quickly as any other language.

Additionally, being able to tell at a glance what sort of data functions require and return saves a ton of reading and thinking about libraries and even code I wrote myself last week. And the benefits of Cargo in quickly building complex projects cannot be overstated.

All that considered, I find Rust to be quite a bit faster to write software in than C++, which is probably it's closest competitor in terms of capabilities. This can be seen at a macro scale in how quickly the Rust library ecosystem has grown.


I disagree. I've been writing heavy Rust for 5 years, and there are many tasks for which what you say is true. The problem is Rust is a low level language, so there is often ceremony you have to go through, even if it doesn't give you value. Simple lifetimes aren't too bad, but between that and trait bounds on some one else traits that have 6 or 7 associated types, it can get hairy FAST. Then consider a design that would normally have self referential structs, or uses heavy async with pinning, async cancellation, etc. etc.

I do agree that OFTEN you can get good velocity, but there IS a cost to any large scale program written in Rust. I think it is worth it (at least for me, on my personal time), but I can see where a business might find differently for many types of programs.


> The problem is Rust is a low level language so there is often ceremony you have to go through, even if it doesn't give you value.

As is C++ which I compared it to, where there is even more boilerplate for similar tasks. I spent so much time working with C++ just integrating disparate build systems in languages like Make and CMake which just evaporates to nothing in Rust. And that's before I even get to writing my code.

> I do agree that OFTEN you can get good velocity, but there IS a cost to any large scale program written in Rust.

I'm not saying there's no cost. I'm saying that in my experience (about 4 years into writing decently sized Rust projects now, 20+ years with C/C++) the cost is lower than C++. C++ is one of the worst offenders in this regard, as just about any other language is easier and faster to write software in, but also less capable for odd situations like embedded, so that's not a very high bar. The magical part is that Rust seems just as capable as C++ with a somewhat lower cost than C++. I find that cost with Rust often approaches languages like Python when I can just import a library and go. But Python doesn't let me dip down to the lower level when I need to, whereas C++ and Rust do. Of the languages which let me do that, Rust is faster for me to work in, no contest.

So it seems like we agree. Rust often approaches the productivity of other languages (and I'd say surpasses some), but doesn't hide the complexity from you when you need to deal with it.


> I don't find that to be the case. It may be slower for a month or two while you learn how to work with the borrow checker, but after the adjustment period, the ideas flow just as quickly as any other language.

I was responding to "as any other language". Compared to C++, yes, I can see how iteration would faster. Compared to C#/Go/Python/etc., no, Rust is a bit slower to iterate for some things due to need to provide low level details sometimes.


> Rust is a bit slower to iterate for some things due to need to provide low level details sometimes.

Sometimes specific tasks in Rust require a little extra effort - like interacting with the file picker from WASM required me to write an async function. In embedded sometimes I need to specify an allocator or executor. Sometimes I need to wrap state that's used throughout the app in an Arc(Mutex()) or the like. But I find that there are things like that in all languages around the edges. Sometimes when I'm working in Python I have to dip into C/C++ to address an issue in a library linked by the runtime. Rust has never forced me to use a different language to get a task done.

I don't find the need to specify types to be a particular burden. If anything it speeds up my development by making it clearer throughout the code what I'm operating on. The only unsafe I've ever had to write was for interacting with a GL shader, and for binding to a C library, just the sort of thing it's meant for, and not really possible in those other languages without turning to C/C++. I've always managed to use existing datastructures or composites thereof, so that helps. But that's all you get in languages like C#/Go/Python/etc. as well.

The big change for me was just learning how to think about and structure my code around data lifetimes, and then I got the wonderful experience other folks talk about where as soon as the code compiles I'm about 95% certain it works in the way I expect it to. And the compiler helps me to get there.


There is a real argument to be made that quick prototyping in Rust is unintuitive compared to other languages, however it's definitely possible and does not even impact iteration speed all that much: the only cost is some extra boilerplate, without even needing to get into `unsafe` code. You don't get the out-of-the-box general tracing GC that you have in languages like Golang, Java/C# or ECMAScript, or the bignum-by-default arithmetic of Python, but pretty much every other basic facility is there, including dynamic variables (the `Any` trait).

In an ideal world, where computing software falls under the same liability laws as everything else, there is no shipping without correctness.

Unfortunately too many people accept using computers requires using broken produts, something that most people would return on the same day with other kind of goods.


> Rust makes it easy to write correct software quickly, but it’s slower for writing incorrect software that still works for an MVP

YMMV on that, but IMHO the bigger part of that is the ecosystem , especially for back-end. And by that metric, you should never use anything else than JS for prototyping.

Go will also be faster than Rust to prototype backend stuff with because most of what you need is in the standard library. But not by a large margin and you'll lose that benefit by the time you get to production.

I think most people vastly overestimate the friction added by the borrow checker once you get up to speed.


Funny that you mentioned Erlang since Actors and message passing are tricky to implent in Rust (yes, I’ve seen Tokio). There is a readon why Rust doesnt have a nice GUI library, or a nice game engine. Resources must be shared, and there is more to sharing than memory ownership.

> it "just requires" you to flawlessly uphold memory access invariants manually across all possible concurrent states of your program.

No it doesn't. Zig doesn't require you to think about concurrency at all. You can just not do concurrency.

> Stop beating around the bush. Rust is just easier than nearly any other language for writing concurrent programs

This is entirely unrelated to the problem of defining shared global state.

    var x: u64 = 10;
There. I defined shared global state without caring about writing concurrent programs.

Rust (and you) makes an assertion that all code should be able to run in a concurrent context. Code that passes that assertion may be more portable than code that does not.

What is important for you to understand is: code can be correct under a different set of assertions. If you assert that some code will not run in a concurrent environment, it can be perfectly correct to create a mutable global variable. And this assertion can be done implicitly (ie: I wrote the program knowing I'm not spawning any threads, so I know this variable will not have shared mutable access).


Rust doesn't require you to think about concurrency if you don't use it either. For global variables you just throw in a thread_local. No unsafe required.

> This is entirely unrelated to the problem of defining shared global state

In it's not. The only thing that makes having a shared global state unsafe in Rust is the fact that this “global” state is shared across threads.

If you know you want the exact same guarantees as in Zig (that is code that will work as long as you don't use multiple threads but will be UB if you do) then it's just: static mut x: u64 = 0;

The only difference between Zig and Rust being that you'll need to wrap access to the shared variable in an unsafe block (ideally with a comment explaining that it's safe as long as you do it from only one thread).

See https://doc.rust-lang.org/nightly/reference/items/static-ite...


> Rust (and you) makes an assertion that all code should be able to run in a concurrent context.

It really doesn't. Rust's standard library does to an extent, because rust's standard library gives you ways to run code in concurrent contexts. Even then it supports non-concurrent primitives like thread locals and state that can't be transferred or shared between threads and takes advantage of that fact. Rust the language would be perfectly happy for you to define a standard library that just only supports the single threaded primitives.

You know what's not (generally) safe in a single threaded context? Mutable global variables. I mean it's fine for an int so long as you don't have safe ways to get pointer types to it that guarantee unique access (oops, rust does. And it's really nice for local reasoning about code even in single threaded contexts - I wouldn't want to give them up). But as soon as you have anything interesting, like a vector, you get invalidation issues where you can get references to memory it points to that you can then free while you're still holding the reference and now you've got a use after free and are corrupting random memory.

Rust has a bunch of abstractions around the safe patterns though. Like you can have a `Cell<u64>` instead of a `u64` and stick that in a thread local and access it basically like a u64 (both reading and writing), except you can't get those pointers that guarantee nothing is aliasing them to it. And a `Cell<Vec<u64>>` won't let you get references to the elements of the vector inside of it at all. Or a `RefCell<_>` which is like a RwLock except it can't be shared between threads, is faster, and just crashes instead of blocking because blocking would always result in a deadlock.


I mean I get what you are saying but part of the problem is today this will be true tomorrow some poor chap maintaining the code will forget/misunderstand the intent and hello undefined behavior.

I am glad that there is such comment among countless that try their best to convince that Rust way is just the best way to do stuff, whatever the context.

But no, clearly there is no cult build around Rust, and everyone that suggest otherwise is dishonest.


> it "just requires" you to flawlessly uphold memory access invariants manually across all possible concurrent states of your program.

Which, for certain kinds of programs, is trivially simple for e.g. "set value once during early initialization, then only read it". No, it's not thread-local. And even for "okay, maybe atomically update it once in a blue moon from one specific place in code" scenario is pretty easy to do locklessly.


Sure, and those use cases are just as easily supported by Rust, in a more principled manner that directly encodes the intent and resists misuse by people modifying the code later.

This is really it to me. It's like saying, "look people it's so much easier to develop and build an airplane when you don't have to adhere to any rules". Which of course is true. But I don't want to fly in any of those airplanes, even if they are designed and build by the best and brightest on earth.

I find Elixir and Erlang easier, but I'm still a neophyte with Rust, so I may feel differently in a year.

>it "just requires" you to flawlessly uphold memory access invariants manually across all possible concurrent states of your program.

The difference is it doesn't prevent you so it doesn't "just require"


Sure, it only "just requires" it if you actually care about your program working properly in the presence of concurrency. To reiterate, this is as true in Rust as it is in C or Zig, it's just that also Rust allows you to do better than the "YOLO" approach to concurrency in a way that most languages could only dream of.

Seriously, I'm begging people to try writing a program that uses ordinary threads in Rust via `std::thread::scope`, it's eye-opening how lovely thread-based concurrency is when you have modern tools at your disposal.


Is it easier than golang?

https://www.ralfj.de/blog/2025/07/24/memory-safety.html

Go is by default not thread safe. Here the author shows that by looping

    for {
        globalVar = &Ptr { val: &myval }
        globalVar = &Int { val: 42 }
     }
You can create a pointer with value 42 as the type and value are two different words and are not updated atomically

So I guess go is easier to write, but not with the same level of safety


Go is easy until one needs to write multithreaded code with heavy interactions between threads. Channels are not powerful enough to express many tasks, explicit mutexes are error prone and Context hack to support cancellation is ugly and hard to use correctly.

Rust channels implemented as a library are more powerful covering more cases and explicit low-level synchronization is memory-safe.

My only reservation is the way async was implemented in Rust with the need to poll futures. As a user of async libraries it is very ok, but when one needs to implement a custom future it complicates things.


Rust is a 99% solution to a 1% problem.

Presumably this is gray because it's a quip, but I think that's about right. So, so, so much rust is being written for applications that just don't need it. For almost everything[1] a managed runtime like Go or Java or .NET is going to be just as effectively deployed, significantly cheaper to develop and much cheaper to maintain.

And the situations where you really need a "systems programming" environment have been really at best a wash with Rust. It's mostly replacing boring middleware (c.f. the linked article). Where are the rustacean routing engines and database backends and codecs and kernels? Not in deployment anywhere, not yet. C still rules that world, even for new features.

[1] Well, everything big enough to need a typesafe high performance platform. The real "everything", to first approximation, should be in python.


> For almost everything a managed runtime like Go or Java or .NET is going to be just as effectively deployed, significantly cheaper to develop

That might be true. I personally still prefer to use a language with sum-types and exhaustive pattern matching for encoding business logic.

> and much cheaper to maintain.

[citation needed]

> Where are the rustacean routing engines and database backends and codecs and kernels? Not in deployment anywhere, not yet.

It is used at Amazon on Firecracker, S3, EC2, CloudFront, Route 53, and that's just what was publicly talked about in 2020[0].

It is used in Android, including in the Kernel[1].

It is used at Microsoft, including in the Kernel[2].

It is used extensively in Firefox, and less extensively in Chrome. JPEG XL might be reincorporated into them because there's a Rust codec in the works.

For databases, the earliest I remember is TiKV[3], which hit 1.0 back in 2018. There are others since.

> C still rules that world, even for new features.

Sure. So?

[0]: https://aws.amazon.com/blogs/opensource/why-aws-loves-rust-a...

[1]: https://security.googleblog.com/2025/11/rust-in-android-move...

[2]: https://www.thurrott.com/windows/282471/microsoft-is-rewriti...

[3]: https://github.com/tikv/tikv


It they had not messed up async it would be much better

Given the constraints I still haven’t seen an asynchronous proposal for Rust that would do things differently.

Keep in mind that one requirement is being able to create things like Embassy.

https://github.com/embassy-rs/embassy


I agree, I think they should have delayed it.

In a different universe rust still does not have async and in 5 years it might get an ocaml-style effect system.


And in that universe Rust is likely an inconsequential niche language.

If rust skipped async features I think it would not have damaged it much

Almost the entire early large scale adoption came from people writing async web services.

From 2023:

> In that regard, async/await has been phenomenally successful. Many of the most prominent sponsors of the Rust Foundation, especially those who pay developers, depend on async/await to write high performance network services in Rust as one of their primary use cases that justify their funding.

https://without.boats/blog/why-async-rust/


It just requires unsafe. One concept, and then you can make a globally mutable variable.

And it's a good concept, because it makes people feel a bit uncomfortable to type the word "unsafe", and they question whether a globally mutable variable is in fact what they want. Which is great! Because this is saving every future user of that software from concurrency bugs related to that globally mutable variable, including ones that aren't even preserved in the software now but that might get introduced by a later developer who isn't thinking about the implications of that global unsafe!


A language that makes making a global mutable variable feel like making any other binding is a anti-pattern and something I'm glad Rust doesn't try to pretend is the same thing.

If you treat shared state like owned state, you're in for a bad time.


Nah, learning Rust is trivial. I've done it 3 or 4 times now.

In how many lifetimes?

lifetimes is Err. Returning to caller.

‘static

> Rust has jumped that particular shark. It will never be trivial, period.

Maybe, but the language being hard in aggregate is very different from the quoted claim that this specific thing is hard.


Well-designed programming languages should disincentivize from following a wrong practice and Rust is following the right course here.

The caveat here is that there is a complexity cost in borrowing mechanics and for a large number of applications it might not be the best option.

He’s talking about adding a keyword. That is all. I’d call that trivial.

Except really the invocation of `unsafe` should indicate maybe you actually don't know what you're doing and there might be a safe abstraction like a mutex or something which does what you need.

Sure, of course. It's an aptly named keyword.

so does the rust compiler check for race conditions between threads at compile time? if so then i can see the allure of rust over c, some of those sync issues are devilish. and what about situations where you might have two variables closely related that need to be locked as a pair whenever accessed.

> so does the rust compiler check for race conditions between threads at compile time?

My understanding is that Rust prevents data races, but not all race conditions. You can still get a logical race where operations interleave in unexpected ways. Rust can’t detect that, because it’s not a memory-safety issue.

So you can still get deadlocks, starvation, lost wakeups, ordering bugs, etc., but Rust gives you:

- No data races

- No unsynchronized aliasing of mutable data

- Thread safety enforced through type system (Send/Sync)


and you can have good races too (where the order doesnt matter)

Yeah it's worth emphasizing, if I spawn two threads, and both of them print a message when they finish (and don't interact with each other in any other way), that's technically a race condition. The output of my program depends on the order on which these threads complete. The question is whether it's a race that I care about.

> what about situations where you might have two variables closely related that need to be locked as a pair whenever accessed.

This fits quite naturally in Rust. You can let your mutex own the pair: locking a `Mutex<(u32, u32)>` gives you a guard that lets you access both elements of the pair. Very often this will be a named `Mutex<MyStruct>` instead, but a tuple works just as well.


In rust, there are two kinds of references, exclusive (&mut) and shared(&). Rustc guarantees you that if you provide an exclusive reference, no other thread will have that. If your thread has an exclusive reference, then it can mutate the contents of the memory. Rustc also guarantees that you won't end up with a dropped reference inside of your threads, so you will always have allocated memory.

Because rust guarantees you won't have multiple exclusive (and thus mutable refs), you won't have a specific class of race conditions.

Sometimes however, these programs are very strict, and you need to relax these guarantees. To handle those cases, there are structures that can give you the same shared/exclusive references and borrowing rules (ie single exclusive, many shared refs) but at runtime. Meaning that you have an object, which you can reference (borrow) in multiple locations, however, if you have an active shared reference, you can't get an exclusive reference as the program will (by design) panic, and if you have an active exclusive reference, you can't get any more references.

This however isn't sufficient for multithreaded applications. That is sufficient when you have lots of pieces of memory referencing the same object in a single thread. For multi-threaded programs, we have RwLocks.

https://doc.rust-lang.org/std/cell/index.html


This was a primary design goal for Rust! To prevent data races (and UAF and other types of memory unsafety) by construction through the type system.

No, it does not.

Rust approach to shared memory is in-place mutation guarded by locks. This approach is old and well-know, and has known problems: deadlocks, lock contention, etc. Rust specifically encourages coarse-granular locks by design, so lock contention problem is very pressing.

There are other approaches to shared memory, like ML-style mutable pointers to immutable data (perfected in Clojure) and actors. Rust has nothing to do with them, and as far as I understand the core choices made by the language make implementing them very problematic.


> There are other approaches to shared memory, like ML-style mutable pointers to immutable data (perfected in Clojure) and actors. Rust has nothing to do with them, and as far as I understand the core choices made by the language make implementing them very problematic.

Would you mind elaborating on this? At least off the top of my head a mut Arc<T> seems like it should suffice for a mutable pointer to immutable data, and it's not obvious to me what about actors makes implementing them in Rust very problematic.


mutable pointers to immutable data is the ticket.

It entirely prevents race conditions due to the borrow checker and safe constructs like Mutexes.

Logical race conditions and deadlocks can still happen.


Rust's specific claims are that safe Rust is free from data races, but not free from general race conditions, including deadlocks.

ah i see, thanks. i have no idea what rust code looks like but from the article it sounds like a language where you have a lot of metadata about the intended usage of a variable so the compiler can safety check. thats its trick.

That's a fairly accurate idea of it. Some folks complain about Rust's syntax looking too complex, but I've found that the most significant differences between Rust and C/C++ syntax are all related to that metadata (variable types, return types, lifetimes) and that it's not only useful for the compiler, but helps me to understand what sort of data libraries and functions expect and return without having to read through the entire library or function to figure that out myself. Which obviously makes code reuse easier and faster. And similarly allows me to reason much more easily about my own code.

I think you’re misconstruing the argument. Those of us that dislike the rust syntax feel at least that strongly about c++. They’re both disasters.

The only thing I really found weird syntactically when learning it was the single quote for lifetimes because it looks like it’s an unmatched character literal. Other than that it’s a pretty normal curly-braces language, & comes from C++, generic constraints look like plenty of other languages.

Of course the borrow checker and when you use lifetimes can be complex to learn, especially if you’re coming from GC-land, just the language syntax isn’t really that weird.


Agreed. In practice Rust feels very much like a rationalized C++ in which 30 years of cruft have been shrugged off. The core concepts have been reduced to a minimum and reinforced. The compiler error messages are wildly better. And the tooling is helpful and starts with opinionated defaults. Which all leads to the knock-on effect of the library ecosystem feeling much more modular, interoperable, and useful.

It's really an ML with type classes and a better syntax (and a non-stupid module sublanguage) that also just happens to be more C-like.

Thread safety metadata in Rust is surprisingly condensed! POSIX has more fine-grained MT-unsafe concepts than Rust.

Rust data types can be "Send" (can be moved to another thread) and "Sync" (multiple threads can access them at the same time). Everything else is derived from these properties (structs are Send if their fields are Send. Wrapping non-Sync data in a Mutex makes it Sync, thread::spawn() requires Send args, etc.)

Rust doesn't even reason about thread-safety of functions themselves, only the data they access, and that is sufficient if globals are required to be "Sync".


well no ackchyually

If I created a new programming language I would just outright prohibit mutable global variables. They are pure pure pure evil. I can not count how many times I have been pulled in to debug some gnarly crash and the result was, inevitably, a mutable global variable.

> They are pure pure pure evil.

They are to be used with caution. If your execution environment is simple enough they can be quite useful and effective. Engineering shouldn't be a religion.

> I can not count how many times I have been pulled in to debug some gnarly crash and the result was, inevitably, a mutable global variable.

I've never once had that happen. What types of code are you working on that this occurs so frequently?


> If your execution environment is simple enough they can be quite useful and effective

Saud by many an engineer whose code was running in systems that were in fact not that simple!

What is irksome is that globals are actually just kinda straight worse. Like the code that doesn't use a singleton and simply passes a god damn pointer turns out to be the simpler and easier thing to do.

> What types of code are you working on that this occurs so frequently?

Assorted C++ projects.

It is particularly irksome when libraries have globals. No. Just no never. Libraries should always have functions for "CreateContext" and "DestroyContext". And the public API should take a context handle.

Design your library right from the start. Because you don't know what execution environments will run in. And it's a hell of a lot easier to do it right from the start than to try and undo your evilness down the road.

All I want in life is a pure C API. It is simple and elegant and delightful and you can wrap it to run in any programming environment in existence.


> All I want in life is a pure C API. It is simple and elegant and delightful and you can wrap it to run in any programming environment in existence.

Sure thing boss, here's that header file populated exclusively by preprocessor macros that you asked for.


Super weird reply.

You need to be pragmatic and practical. Extra large codebases have controllers/managers that must be accessible by many modules. A single global vs dozens of local references to said “global” makes code less practical.

One of my favorite talks of all-time is the GDC talk on Overwatch's killcam system. This is the thing that when you die in a multiplayer shooter you get to see the last ~4 seconds of gameplay from the perspective of your killer. https://www.youtube.com/watch?v=A5KW5d15J7I

The way Blizzard implemented this is super super clever. They created an entirely duplicate "replay world". When you die the server very quickly "backfills" data in the "replay world". (Server doesn't send all data initially to help prevent cheating). The camera then flips to render the "replay world" while the "gameplay world" continues to receives updates. After a few seconds the camera flips back to the "gameplay world" which is still up-to-date and ready to rock.

Implementing this feature required getting rid of all their evil dirty global variables. Because pretty much every time someone asserted "oh we'll only ever have one of these!" that turned out to be wrong. This is a big part of the talk. Mutables globals are bad!

> Extra large codebases have controllers/managers that must be accessible by many modules.

I would say in almost every single case the code is better and cleaner to not use mutable globals. I might make a begrudging exception for logging. But very begrudgingly. Go/Zig/Rust/C/C++ don't have a good logging solution. Jai has an implict context pointer which is clever and interesting.

Rust uses the unsafe keyword as an "escape hatch". If I wrote a programming language I probably would, begrudgingly, allow mutable globals. But I would hide their declaration and usage behind the keyworld `unsafe_and_evil`. Such that every single time a programmer either declared or accessed a mutable global they would have to type out `unsafe_and_evil` and acknowledge their misdeeds.


If we're getting philosophical, we can identify a hierarchy of globals:

1. Read-only (`const`s in Rust). These are fine, no objections.

2. Automatic-lazily-initialized write-once, read-only thereafter (`LazyLock` in Rust). These are also basically fine.

3. Manually-initialized write-once, read-only thereafter (`OnceLock` in Rust). These are also basically fine, but slightly more annoying because you need to be sure to manually cover all possible initialization pathways.

4. Write-only. This is where loggers are, and these are also basically fine.

5. Arbitrary read/write. This is the root of all evil, and what we classically mean when we say "global mutable state".


Good list.

2 and 3 are basically fine. Just so long as you don’t rely on initialization order. And don’t have meaningful cleanup. C++ initialization fiasco is great pain. Crash on shutdown bugs are soooo common with globals.

4 of have to think about.

And yes 5 is the evilness.


Could you describe what you would consider a good logging solution?

Haven’t found it yet! Jai’s implicit context pointer is interesting. Need to work with it more. It still has lots of limitation. But interesting angle.

This is a great example of something that experience has dragged me, kicking and screaming, into grudgingly accepting: That ANY time you say “We will absolutely always only need one of these, EVER” you are wrong. No exceptions. Documents? Monitors? Mouse cursors? Network connections? Nope.

Testing is such a good counter example. "We will absolutely always only need one of these EVER". Then, uh, can you run your tests in parallel on your 128-core server? Or are you forced to run tests sequentially one at a time because it either utterly breaks or accidentally serializes when running tests in parallel? Womp womp sad trombone.

There was an interesting proposal in the rust world to try and handle that with a form of implicit context arguments... I don't have time to track down all the various blogposts about it right now but I think this was the first one/this comment thread will probably have links to most of it: https://internals.rust-lang.org/t/blog-post-contexts-and-cap...

Anyways, I think there are probably better solutions to the problem than globals, we just haven't seen a language quite solve it yet.


In my programming language (see my latest submission) I wanted to do so. But then I realized, that in rare cases global mutable variables (including thread-local ones) are necessary. So, I added them, but their usage requires using an unsafe block.

Not really possible in a systems level programming language like rust/zig/C. There really is only one address space for the process... and if you have the ability to manipulate it you have global variables.

There's lots of interest things you could do with a rust like (in terms of correctness properties) high level language, and getting rid of global variables might be one of them (though I can see arguments in both directions). Hopefully someone makes a good one some day.


> Not really possible in a systems level programming language like rust/zig/C. There really is only one address space for the process... and if you have the ability to manipulate it you have global variables.

doesn't imply you have to expose it as a global mutable variable



That seems unusual. I would assume trivial means the default approach works for most cases. Perhaps mutable global variables are not a common use case. Unsafe might make it easier, but it’s not obvious and probably undesired. I don’t know Rust, but I’ve heard pockets of unsafe code in a code base can make it hard to trust in Rust’s guarantees. The compromise feels like the language didn’t actually solve anything.

Outside of single-initialization/lazy-initialization (which are provided via safe and trivial standard library APIs: https://doc.rust-lang.org/std/sync/struct.LazyLock.html ) almost no Rust code uses global mutable variables. It's exceedingly rare to see any sort of global mutable state, and it's one of the lovely things about reading Rust code in the wild when you've spent too much of your life staring at C code whose programmers seemed to have a phobia of function arguments.

> It's exceedingly rare to see any sort of global mutable state I know a bit of Rust, so you don't need to explain in details. How to use a local cache or db connection pool in Rust (both of them, IMO, are the right use case of global mutable state)?

You wrap it in a mutex and then it is allowed.

Global state is allowed. It just has to be thread safe.


Why does that have to be global? You can still pass it around. If you don't want to clobber registers, you can still put it in a struct. I don't imagine you are trying to avoid the overhead of dereferencing a pointer.

I think a better example might be logging. How is this typically solved in Rust? Do you have to pass a Logger reference to every function that potentially wants to log something? (In C++ you would typically have functions/macros that operate on a global logger instance.)

In Rust you typically use the "log" crate, which also has a global logger instance [0]. There is also "tracing" which uses thread local storage.

As another comment said, global state is allowed. It just has to be proven thread-safe via Rust's Send and Sync traits, and 'static lifetime. I've used things like LazyLock and ArcSwap to achieve this in the past.

[0] https://docs.rs/log/latest/log/fn.set_logger.html


I wonder what the advantage of passing it around is when it makes the argument list longer. The only advantage that I can see is that it emphasizes that this function does something with cache.

The default approach is to use a container that enforces synchronization. If you need manual control, you are able to do that, you just need to explicitly opt into the responsibility that comes with it.

If you use unsafe to opt out of guarantees that the compiler provides against data races, it’s no different than doing the exact same thing in a language that doesn’t protect against data races.


So I've got a crate I built that has a type that uses unsafe. Couple of things I've learned. First, yes, my library uses unsafe, but anyone who uses it doesn't have to deal with that at all. It behaves like a normal implementation of its type, it just uses half the memory. Outside of developing this one crate, I've never used unsafe.

Second, unsafe means the author is responsible for making it safe. Safe in rust means that the same rules must apply as unsafe code. It does not mean that you don't have to follow the rules. If one instead used it to violate the rules, then the code will certainly cause crashes.

I can see that some programmers would just use unsafe to "get around a problem" caused by safe rust enforcing those rules, and doing so is almost guaranteed to cause crashes. If the compiler won't let you do something, and you use unsafe to do it anyway, there's going to be a crash.

If instead we use unsafe to follow the rules, then it won't crash. There are tools like Miri that allow us to test that we haven't broken the rules. The fact that Miri did find two issues in my crate shows that unsafe is difficult to get right. My crate does clever bit-tricks and has object graphs, so it has to use unsafe to do things like having back pointers. These are all internal, and you can use the crate in safe rust. If we use unsafe to implement things like doubly-linked lists, then things are fine. If we use unsafe to allow multiple threads to mutate the same pointers (Against The Rules), then things are going to crash.

The thing is, when you are programming in C or C++, it's the same as writing unsafe rust all the time. In C/C++, the "pocket of unsafe code" is the entire codebase. So sure, you can write safe C, like I can write safe "unsafe rust". But 99% of the code I write is safe rust. And there's no equivalent in C or C++.


> I would assume trivial means the default approach works for most cases.

I mean, it does. I'm not sure what you consider the default approach, but to me it would be to wrap the data in a Mutex struct so that any thread can access it safely. That works great for most cases.

> Perhaps mutable global variables are not a common use case.

I'm not sure how common they are in practice, though I would certainly argue that they shouldn't be common. Global mutable variables have been well known to be a common source of bugs for decades.

> Unsafe might make it easier, but it’s not obvious and probably undesired.

All rust is doing is forcing you to acknowledge the trade-offs involved. If you want safety, you need to use a synchronization mechanism to guard the data (and the language provides several). If you are ok with the risk, then use unsafe. Unsafe isn't some kind of poison that makes your program crash, and all rust programs use unsafe to some extent (because the stdlib is full of it, by necessity). The only difference between rust and C is that rust tells you right up front "hey this might bite you in the ass" and makes you acknowledge that. It doesn't make that global variable any more risky than it would've been in any other language.


> I would assume trivial means the default approach works for most cases. Perhaps mutable global variables are not a common use case. Unsafe might make it easier, but it’s not obvious and probably undesired.

I'm a Rust fan, and I would generally agree with this. It isn't difficult, but trivial isn't quite right either. And no, global vars aren't terribly common in Rust, and when used, are typically done via LazyLock to prevent data races on intialization.

> I don’t know Rust, but I’ve heard pockets of unsafe code in a code base can make it hard to trust in Rust’s guarantees. The compromise feels like the language didn’t actually solve anything.

Not true at all. First, if you aren't writing device drivers/kernels or something very low level there is a high probability your program will have zero unsafe usages in it. Even if you do, you now have an effective comment that tells you where to look if you ever get suspicious behavior. The typical Rust paradigm is to let low level crates (libraries) do the unsafe stuff for you, test it thoroughly (Miri, fuzzing, etc.), and then the community builds on these crates with their safe programs. In contrast, C/C++ programs have every statement in an "unsafe block". In Rust, you know where UB can or cannot happen.


> Even if you do, you now have an effective comment that tells you where to look if you ever get suspicious behavior.

By the time suspicious behavior happens, isn’t it kind of a critical inflection point?

For example, the news about react and next that came out. Once the code is deployed, re-deploying (especially with a systems language that quite possibly lives on an air-gapped system with a lot of rigor about updates) means you might as well have used C, the dollar cost is the same.


Are you with a straight face saying that occasionally having a safety bug in limited unsafe areas of Rust is functionally the same as having written the entire program in an unsafe language like C?

One, the dollar cost is not the same. The baseline floor of quality will be higher for a Rust program vs. a C program given equal development effort.

Second, the total possible footprint of entire classes of bugs is zero thanks to design features of Rust (the borrowck, sum types, data race prevention), except in a specifically delineated areas which often total zero in the vast majority of Rust programs.


> The baseline floor of quality will be higher for a Rust program vs. a C program given equal development effort.

Hmm, according to whom, exactly?

> Second, the total possible footprint of entire classes of bugs is zero thanks to design features of Rust (the borrowck, sum types, data race prevention), except in a specifically delineated areas which often total zero in the vast majority of Rust programs.

And yet somehow the internet went down because of a program written in rust that didn’t validate input.


> Hmm, according to whom, exactly?

Well, Google for one. https://security.googleblog.com/2025/11/rust-in-android-move...

> And yet somehow the internet went down because of a program written in rust that didn’t validate input.

You're ignoring other factors (it wasn't just Cloudflare's rust code that led to the issue), but even setting that aside your framing is not accurate. The rust program went down because the programmer made a choice that, given invalid input, it should crash. This could happen in every language ever made. It has nothing to do with rust.


Google's Android teams also categorize old C code as C++, and mix gotos into their modern C++ code.

> This could happen in every language ever made. It has nothing to do with rust.

Except it does. This also has to do with culture. In Rust, I get the impression that one can set it up as roughly two communities.

The first does not consider safety, security and correctness to be the responsibility of the language, instead they consider it their own responsibility. They merely appreciate it when the language helps with all that, and take precautions when the language hinders that. They try to be honest with themselves.

The second community is careless, might make various unfounded claims and actions that sometimes border on cultish and gang mob behavior and beliefs, and can for instance spew unwrap() all over codebases even when not appropriate for that kind of project, or claim that a Rust project is memory safe even when unsafe Rust is used all over the place with lots of basic bugs and UB-inducing bugs in it.

The second community is surprisingly large, and is severely detrimental to security, safety and correctness.


Again, this has nothing to do with the point at hand, which is that "in any language, a developer can choose the crash the problem if a unrecoverable state happens". That's it.

Tell me about how these supposed magical groups have anything at all to do with language features. What language can magically conjure triple the memory from thin air because the upstream query returned 200+ entries instead of the 60-ish you're required to support?


I don't think you're actually disagreeing with the person you're responding to here. Even if you take your grouping as factual, there's nothing that limits said grouping to Rust programmers. Or in other words:

> This could happen in every language ever made. It has nothing to do with rust.


> And yet somehow the internet went down because of a program written in rust that didn’t validate input.

What? The Cloudflare bug was from a broken system configuration that eventually cascaded into (among other things) a Rust program with hardcoded limits that crashed loudly. In no way did that Rust program bring down the internet; it was the canary, not the gas leak. Anybody trying to blame Rust for that event has no idea what they're talking about.


> And yet somehow the internet went down because of a program written in rust that didn’t validate input.

Tell me which magic language creates programs free of errors? It would have been better had it crashed and compromised memory integrity instead of an orderly panic due to an invariant the coder didn't anticipate? Type systems and memory safety are nice and highly valuable, but we all know as computer scientists we have yet to solve for logic errors.


> And yet somehow the internet went down because of a program written in rust that didn’t validate input.

No, it _did validate_ the input, and since that was invalid it resulted in an error.

People can yap about that unwrap all they want, but if the code just returned an error to the caller with `?` it would have resulted in a HTTP 500 error anyway.


> might as well have used C, the dollar cost is the same.

When your unsafe area is small, you put a LOT of thought/testing into those small blocks. You write SAFETY comments explaining WHY it is safe (as you start with the assumption there will be dragons there). You get lots of eyeballs on them, you use automated tools like miri to test them. So no, not even in the same stratosphere as "might as well have used C". Your probability of success vastly higher. A good Rust programmer uses unsafe judiciously, where as a C programmer barely blinks as they need ensure every single snippet of their code is safe, which in a large program, is an impossible task.

As an aside, having written a lot of C, the ecosystem and modern constructs available in Rust make writing large scale programs much easier, and that isn't even considering the memory safety aspect I discuss above.


SAFETY comments do not magically make unsafe Rust correct nor safe. And Miri cannot catch everything, and is magnitudes slower than regular program running.

https://github.com/rust-lang/rust/commit/71f5cfb21f3fd2f1740...

https://materialize.com/blog/rust-concurrency-bug-unbounded-...


I think you might be misreading GP's comment. They are not claiming that SAFETY comments and MIRI guarantee correctness/safety; those are just being used as examples of the extra effort that can be and are expended on the relatively few unsafe blocks in your codebase, resulting in "your probability of success [being] vastly higher" compared to "might as well have used C".

This just skips the:

> First, if you aren't writing device drivers/kernels or something very low level there is a high probability your program will have zero unsafe usages in it.

from the original comment. Meanwhile all C code is implicitly “unsafe”. Rust at least makes it explicit!

But even if you ignore memory safety issues bypassed by unsafe, Rust forces you to handle errors, it doesn’t let you blow up on null pointers with no compiler protection, it allows you to represent your data exhaustively with sum types, etc etc etc


Isn’t rust proffered up as a systems language? One that begged to be accepted into the Linux kernel?

Don’t device drivers live in the Linux kernel tree?

So, unsafe code is generally approved in device driver code?

Why not just use C at that point?


Because writing proper kernel C code requires decades of experience to navigate the implicit conventions and pitfalls of the existing codebase. The human pipeline producing these engineers is drying up because nobody's interested in learning that stuff by going through years of patch rejection from maintainers that have been at it since the beginning.

Rust's rigid type system, compiler checks and insistence on explicitness forces a _culture change_ in the organization. In time, this means that normal developers will regain a chance to contribute to the kernel with much less chance of breaking stuff. Rust not only makes compiled binary more robust but also makes the codebase more accessible.


I am quite certain that someone who has been on HN as long as you have is capable of understanding the difference between 0% compiler-enforced memory safety in a language with very weak type safety guarantees and 95%+ of code regions even in the worst case of low-level driver code that performs DMA with strong type safety guarantees.

Please explain the differences in typical aliasing rules between C and Rust. And please explain posts like

https://chadaustin.me/2024/10/intrusive-linked-list-in-rust/

https://news.ycombinator.com/item?id=41947921

https://lucumr.pocoo.org/2022/1/30/unsafe-rust/


The first two is the same article, but they point out that certain structures can be very hard to write in rust, with linked lists being a famous example. The point stands, but I would say the tradeoff is worth it (the author also mentions at the end that they still think rust is great).

The third link is absolutely nuts. Why would you want to initialize a struct like that in Rust? It's like saying a functional programming language is hard because you can't do goto. The author sets themselves a challenge to do something that absolutely goes against how rust works, and then complains how hard it is.

If you want to do it to interface with non-rust code, writing a C-style string to some memory is easier.


You phrase that as if 0-5% of a program being harder to write disqualifies all the benefits of isolating memory safety bugs to that 0-5%. It doesn't.

And it can easily be more than 5%, since some projects both have lots of large unsafe blocks, and also the presence of an unsafe block can require validation of much more than the block itself. It is terrible of you and overall if my understanding is far better than yours.

And even your argument taken at face value is poor, since if it is much harder, and it is some of the most critical code and already-hard code, like some complex algorithm, it could by itself be worse overall. And Rust specifically have developers use unsafe for some algorithm implementations, for flexibility and performance.


> since if it is much harder, and it is some of the most critical code and already-hard code, like some complex algorithm, it could by itself be worse overall.

(Emphasis added)

But is it worse overall?

It's easy to speculate that some hypothetical scenario could be true. Of course, such speculation on its own provides no reason for anyone to believe it is true. Are you able to provide evidence to back up your speculation?


Even embedded kernels can and regularly do have < 5% unsafe code.

Is three random people saying unsafe Rust is hard supposed to make us forget about C’s legendary problems with UB, nil pointers, memory management bugs, and staggering number of CVEs?

You have zero sense of perspective. Even if we accept the premise that unsafe Rust is harder than C (which frankly is ludicrous on the face of it) we’re talking about a tiny fraction of the overall code of Rust programs in the wild. You have to pay careful attention to C’s issues virtually every single line of code.

With all due respect this may be the singular dumbest argument I’ve ever had the displeasure of participating in on Hacker News.


> Even if we accept the premise that unsafe Rust is harder than C (which frankly is ludicrous on the face of it)

I think there's a very strong dependence on exactly what kind of unsafe code you're dealing with. On one hand, you can have relatively straightforwards stuff like get_unsafe or calling into simpler FFI functions. On the other hand, you have stuff like exposing a safe, ergonomic, and sound APIs for self-referential structures, which is definitely an area of active experimentation.

Of course, in this context all that is basically a nitpick; nothing about your comment hinges on the parenthetical.


[flagged]


> Shold one compare Rust with C or Rust with C++?

Well, you're the one asking for a comparison with C, and this subthread is generally comparing against C, so you tell us.

> Modern C++ provides a lot of features that makes this topic easier, also when programs scale up in size, similar to Rust. Yet without requirements like no universal aliasing. And that despite all the issues of C++.

Well yes, the latter is the tradeoff for the former. Nothing surprising there.

Unfortunately even modern C++ doesn't have good solutions for the hardest problems Rust tackles (yet?), but some improvement is certainly more welcome than no improvement.

> Which is wrong

Is it? Would you be able to show evidence to prove such a claim?




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

Search: