17 ms·
Understanding Memory Management, Part 5: Fighting with Rust
- sidcool 1y agoThis is brilliantly written n
- bnjms 1y agoIm sure it is. I don’t know how to program but read most of part 4 when it appeared here last.
- marsven_422 1y ago[dead]
- Animats 1y agoThis is pretty good. A useful way to think about this: - All data in Rust has exactly one owner. - If you need some kind of multiple ownership, you have to make the owner be a reference-counted cell, such as Rc or Arc. - All data can be accessed by one reader/writer, or N readers, but not both at the same time. - There is both compile time and run time machinery to strictly enforce this. Once you get that, you can see what the borrow checker is trying to do for you.
- Surac 1y agoI really like the text. Giving more light to the memory management of rust will help me understand more of the language. I still think some concepts of rust are over verbose but I slowly understand the hype around rust. I myself use C or C++ but I will „borrow“ some of the rust ideas to make my code even more robust
- ultimaweapon 1y agoI'm coming from C++ now I don't want to use C++ anymore. When C++ was still my primary language I always frustrated with some of its feature like non-destructive move, copy by default and dangling references then I found Rust fixed all of those problems. At the beginning I very frustrated with Rust because the borrow checker prevent me from doing what I usually do in C++ but I keep going.
- jandrewrogers 1y agoI think this really depends on the type of software you develop. For some types of systems code, non-destructive moves are a killer feature because ownership and lifetimes can be intrinsically unknowable at compile-time even in a moved-from context. In these cases, a destructive move could introduce a use-after-free that the compiler can’t see whereas a deferred destruction is idiomatic and behaves the way you would expect. This is less than ideal even in C++, though it addresses some cases perfectly. There is an additional concept of “relocatable” types that lives in the middle ground, which is closer to the Rust concept, but that is not currently part of the language though you can hack your own traits. I don’t think any language currently handles the scope of move semantics cases properly.
- steveklabnik 1y agoThis is a good post! A few comments: > Function Overloads Strictly speaking, Rust doesn't support overloaded functions. Function overloading is when you define the same function but with different arguments, and the language selects the correct one based on the argument type. In this case, it's two different implementations of a trait for two different types. That said, it's close enough that this isn't really an issue, more of just a technical note, since this is a series trying to get into details. > I can't find an explanation in the Rust documentation but I expect the reason is that someone could implement another trait that provides .into_iter() on whatever the x is in for y in x, thus resulting in a compiler error because there would be two candidate implementations. Yep, I'm not sure that there is an official explanation anywhere else, but this is exactly what I would assume as well. This ensures that the correct implementation is called. This is also one of the reasons why adding a trait implementation isn't considered a breaking change, even if it could create a compiler error, because you can always expand it yourself to explicitly select the correct choice. Of course, these situations are usually treated more carefully then they have to be, because breakage isn't fun, even if it's technically allowed. > But wait, you say, I'm doing exactly this in the first program, and indeed you are. It's not the same, as the next paragraphs explain. > We are able to examine the function and realize it's safe, but because the compiler wants to use local reasoning, it's not able to do so. This is a super important point!
- junon 1y ago> I can't find an explanation in the Rust documentation but I expect the reason is that someone could implement another trait that provides .into_iter() on whatever the x is in for y in x, thus resulting in a compiler error because there would be two candidate implementations. Because nothing in Rust is identifier-based. Unlike python, all syntax magic (even the ? operator) relies on traits defined by `core`. for x in y { desugars to let mut iter = IntoIterator::into_iter(y); while let Some(x) = y.next() { and x? desugars to match x { Ok(x) => x, Err(e) => { return Err(From::from(e)); } } and x + y desugars to core::ops::Add::add(x, y) etc. All of those traits are expected to live explicitly in the core crate at well known paths. Otherwise you'd be writing methods with absolutely no idea how the language would interact with it. And if you had a Set type implement `add`, it'd have to accept exactly 2 arguments to be compatible with the language's `add` or something equally as unergonomic. It's traits all the way down! There'd be no explanation needed because it'd be antithetical and contradictory to traits to begin with. Once one understands how traits are intended to be used, the explanation for why there aren't identifier based resolution semantics becomes obvious.
- cornholio 1y agoA 20 page document on how to use basic variables, function calls and methods. Except for the threading paragraph, which is hard in any language, this is all complexity and refactoring pain that Rust hoists onto every programmer every day, for relatively modest benefits, somewhat improved performance and memory usage vs the garbage collected/ref-counted version of the same code. Essentially, you wouldn't and shouldn't make that tradeoff for anything other than system programming.
- baq 1y ago> this is all complexity and refactoring pain that Rust hoists onto every programmer every day This is what you should be doing when working with C/C++, except there is no compiler to call you names there if you don’t. If you’re saying ‘use a GC language unless requirements are strict about it’, yeah hard to disagree.
- francasso 1y ago> This is what you should be doing when working with C/C++ I genuinely wonder if you actually have ever written c/c++, there is plenty of code that is perfectly valid and safe (mostly involving multiple pointers to mutable memory being alive) that the borrow check cannot accept because it has to draw a line to things it can prove are correct. It's like saying that the only valid math is the one that an automated theorem prover can prove, it's not even close to being true.
- baq 1y ago> I genuinely wonder if you actually have ever written c/c++ I have; enough for one lifetime if you ask me. It was hunting use after delete which made me stop. I kinda agree with you, with the caveat that both can be true. If you want to write safe-ish C++ you’ll use defensive containers from the start and watch iterators like a hawk. You can also take a more cavalier approach and live with the consequences (which might not happen and then you win big). Rust wants you to basically not use references unless your data fits the one writer xor many readers model (painfully including struct members, recursively) and the cavalier approach is very strongly discouraged on the language level. This forces you towards safety, but I also agree with everyone else who say that isn’t how computers actually work. The impedance mismatch is an engineering tradeoff to make.
- bsaul 1y ago"If we change .set_value() to take a &self instead of a &self" guess it's "instead of a &mut self"
- diath 1y agoThe first code snippet, which is as simple as it gets, perfectly illustrates why Rust is extremely annoying to work with. I understand why you need the into_iter bit and why the borrow checker complains about it, but the fact that even the simplest "for x in y" loop already makes you wrestle the compiler is just poor ergonomics.
- baq 1y ago> wrestle the compiler This is quite literally a skill issue, no offense. 'wrestle the compiler' implies you think you know better; this is usually not the case and the compiler is here to tell you about it. It's annoying to be bad at tracking ownership and guess what: most people are. The ones who aren't have decades of experience in C/C++ and employ much the same techniques that Rust guides you towards. If you really know better, there are ways to get around the compiler. They're verbose and marked unsafe to 1) discourage you from doing that and 2) warn others to be extra careful here. If this is all unnecessary for you - and I want to underscore I agree it should be in most software development work - stick to GC languages. With some elbow grease they can be made as performant as low level languages if you write the critical parts in a way you'd have to do it in Rust and will be free to write the rest in a way which doesn't require years of experience tracking ownership manually. (Note it won't hurt to be tracking ownership anyway, it's just much less of an issue if you have to put a weakref somewhere once a couple years vs be aware at all times of what owns what.)
- 59nadir 1y agoNo, GP can just not use Rust, they don't have to use GC languages to have something that makes sense and doesn't force you to always have a debate with the compiler about even simple things. If they used Odin (or Zig) they could've looped through that dynamic array no problem, in fact: package example import "core:fmt" main :: proc() { xs: [dynamic]int append(&xs, 1, 2) for x in xs { fmt.println(x) } fmt.println(len(xs)) } It is ridiculous that Rust complains even about the simple for loop and to say that this somehow comes down to "Well, everyone would do it this way if they cared about memory safety" is just not really true or valuable input, it sounds like what someone would say if their only systems programming experience came from Rust and they post-rationalized everything they've seen in Rust as being how you have to do it. My tips to people who maybe feel like Rust seems a bit overwrought: Look for something else, check out Odin or Zig, they've got tons of ways of dealing with memory that simply sidestep everything that Rust is about (because inherently Rust and everything that uses RAII has a broken model of how resources should be managed). I learned Odin just by reading its Overview page (https://odin-lang.org/docs/overview/ https://odin-lang.org/docs/overview/) and trying stuff out (nowadays there are also good videos about Odin on YouTube), then found myself productively writing code after a weekend. Now I create 3D engines using just Odin (and we in fact use only a subset of what is on that Overview page). Things can be simple, straight forward and more about the thing you're solving than the language you're using.
- GEORGE3243 1y ago[dead]
- rollcat 1y agoI'm starting to think Zig's strategy to memory management is generally friendlier to a developer. If a function needs to allocate memory, it must take an allocator as a parameter. If it needs scratch space to perform computation, it can use that allocator to create an arena for itself, then free it up before it returns (defer). If it returns a pointer, the caller should assume the object was allocated using that allocator, and becomes the owner. It may still be unclear what happens to a pointer if it's passed as a parameter to another function, but I'd normally consider that a borrow. It's a lot of assumptions, and if you trip, Rust will yell at you much more often than Zig; and it will likely be right to do so. But in all seriousness, I'm tired of the yelling, and find Zig much more pleasant.
- gitroom 1y agobeen banging my head against this same stuff trying to learn rust - honestly memory rules make me miss how easy c feels sometimes, but i'm sticking with it cuz i want fewer bugs
- pjc50 1y agoAll of the same rules exist in C, they're just tracked manually inside the programmer's head and you don't find out about mistakes until much later.
- dmitrygr 1y ago> So why does this result in a move and why does replacing x with &x fix it Precisely the sort of question I do not want to waste time on.
- teleforce 1y agoWe'd expect in the 21st century we have programming languages that have impeccable GC for automatic memory management rather than forcing programmer to wrestling and fighting for manually managing the memory. Auto industry kind of solved this automation mechanism for example with the new high performance Toyota GR Corolla has a new automatic gear transmission that's proven as fast if not faster than the manual version [1]. The same goes to F1, the epitome of car racing performance. [1] 2025 Toyota GR Corolla's New Automatic Gearbox Democratizes Fun: https://www.caranddriver.com/reviews/a62672128/2025-toyota-gr-corolla-automatic-drive/ https://www.caranddriver.com/reviews/a62672128/2025-toyota-g...