10 ms·
> 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. We
by kibwen 11mo 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.
- globalnode 11mo agoso 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.
- ViewTrick1002 11mo agoIt entirely prevents race conditions due to the borrow checker and safe constructs like Mutexes. Logical race conditions and deadlocks can still happen.
- globalnode 11mo agoah 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.
- timschmidt 11mo agoThat'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.
- mh2266 11mo agoThe 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.
- timschmidt 11mo agoAgreed. 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.
- peterfirefly 11mo agoIt'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.
- TylerE 11mo agoI 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.
- pornel 11mo agoThread 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".
- kibwen 11mo agoRust's specific claims are that safe Rust is free from data races, but not free from general race conditions, including deadlocks.
- tczMUFlmoNk 11mo ago> 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.
- PartiallyTyped 11mo agoIn 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 https://doc.rust-lang.org/std/cell/index.html
- andsoitis 11mo ago> 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)
- throwawaymaths 11mo agoand you can have good races too (where the order doesnt matter)
- oconnor663 11mo agoYeah 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.
- treyd 11mo agoThis 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.
- sesm 11mo agoNo, 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.
- aw1621107 11mo ago> 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.
- ClayShentrup 11mo agomutable pointers to immutable data is the ticket.
- deleted 11mo ago[deleted]
- etse 11mo agoThat 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.
- kibwen 11mo agoOutside 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 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.
- dxxvi 11mo ago> 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)?
- therein 11mo agoWhy 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.
- spacechild1 11mo agoI 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.)
- ajross 11mo ago> [...] 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.
- kibwen 11mo ago> 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).
- sharifhsn 11mo agoThis 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.
- timschmidt 11mo ago> 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.
- deleted 11mo ago[deleted]
- forrestthewoods 11mo agoIf 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.
- smallstepforman 11mo agoYou 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.
- gpm 11mo agoThere 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-capabilities-in-rust/15833 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.
- forrestthewoods 11mo agoOne 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 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.
- michaelscott 11mo agoAfter 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
- hu3 11mo agoGlobal 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.
- port11 11mo agoHey, don't tell that to front-end developers, we like our global stores accessible all over.
- iLemming 11mo agoIt's because of the "status quo". Once you start using immutable-first language on the front-end, e.g., Clojurescript - your perspective changes.
- port11 11mo agoI 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.
- ClayShentrup 11mo agoit's the classic Rich hickey talk. simple made easy.
- semiinfinitely 11mo agowell no ackchyually