Thoughts on Go vs. Rust vs. Zig
sinclairtarget.com345 points by yurivish 12 hours ago
345 points by yurivish 12 hours ago
> 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.
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.
> 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.
> 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)
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.
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.
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.
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".
> [...] 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.
Well-designed programming languages should disincentivize from following a wrong practice and Rust is following the right course here.
> 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.
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.
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.
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.
> 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.
> 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.
> 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.
> 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...
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.
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 find Elixir and Erlang easier, but I'm still a neophyte with Rust, so I may feel differently in a year.
Is it easier than golang?
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.
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 atomicallySo I guess go is easier to write, but not with the same level of safety
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.
Rust is a 99% solution to a 1% problem.
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.
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.
Nah, learning Rust is trivial. I've done it 3 or 4 times now.
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.
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!
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.
> 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.
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.
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.
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.
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.
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.
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.
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.
> 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.
> 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.
> 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.
> 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.
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.