6 ms·
Rust Memory Management: Ownership vs. Reference Counting
- ellie_kim98 6mo agoSolid intro. One pattern worth adding: when you're reaching for Rc<RefCell<T>> for graph-like or self-referential data, an index-based arena (Vec<Node> with usize "pointers") is often a third path that keeps you in plain-ownership land — at the cost of pointer identity and some bookkeeping. petgraph, slotmap, and most ECS libraries formalize this. Doesn't replace Arc for genuinely cross-thread shared lifetimes, but it eliminates a lot of cases where people first reach for it.
- jeroenhd 6mo agoThe Vec approach can also have a nice benefit in terms of memory usage if you construct a lot of small structs (say, a linked list-style structure with a value and a forward reference). Rather than using 64 bits for a pointer, you can store an 8/16/32 bit number, depending on how many items you need. If the stars align, it can also help with keeping more items in the CPU cache lines.
- notnullorvoid 6mo agoI'm often curious how well the arena approach works for highly dynamic graphs. You end up having to reimplement the ref counting or a custom GC, which carries more risk. Even memory locality of a arena doesn't provide benefit, because memory order does not mirror execution order.
- jonathanstrange 6mo ago[flagged]
- zigzag312 6mo agoReference counting is a form of garbage collection.
- sieabahlpark 6mo ago[dead]
- jonathanstrange 6mo agoTrue, but it also happens to be the provably the shittiest form of garbage collection that was already outdated in the 90s.
- conradludgate 6mo agoI miss the clear distinction between exclusive access and shared access when I work in GC languages. Knowing clearly when you are allowed/expected to modify a value, and when you are expected to only read the value ends up being very useful and it has poisoned my brain when working in Python/JS/Go where some values are internally pointers and there's no standard to learn once for when a mutation is safe or if you should always make a copy
- quietbritishjim 6mo agoOne way to deal with this in GC languages is to have a separate read-only view on a mutable object. The example that comes to mind, though it's a bit more complex than a view on a simple data structure is CancellationToken (read only) vs CancellationTokenSource (writable in the sense of having "cancel()" method) in C#. I'm not saying it's better than proper mutability support but it does work well enough sometimes.
- baranul 6mo agoRust is becoming less special in this area. Languages such as Dlang, Vlang, and Julia have added optional ownership and borrowing. As these offerings are optional, many can see this as greater programmer freedom to decide what to use for their projects, with languages that are easier to use or read.
- bcjdjsndon 6mo ago[flagged]
- b40d-48b2-979e 6mo agoRust already has.
- kibwen 6mo agoThe point of Rust, without counting `unsafe`, is to eliminate memory errors at compile-time. But the point of Rust, when including `unsafe`, is not to entirely eliminate memory errors at compile-time, but to make it feasible to cordon off the unsafe parts into realistically-auditable sections with documented safety invariants. At this it has been dramatically successful, almost beyond anyone's wildest hopes. I have worked on embedded, bare-metal Rust codebases (i.e. the codebases you would most expect to have to do grotty memory faffing) with fewer than 5% of files containing `unsafe` blocks, whereas high-level unsafe code usually has no `unsafe` blocks whatsoever. It's an incredible force multiplier for writing correct low-level code.
- bcjdjsndon 6mo ago> without counting `unsafe`, Well, if you exclude all the bad code people have wrote, c is a safe language... See the point I'm making here? If coders couldn't be trusted multiple times in the past, and we had to invent language level features to correct them, but they still continued to make either the same, or a new, mistakes.... Why is rust any different? I guarantee you we will be complaining about unsafe rust in the future because rust doesnt really bring anything new to the table other than trivial cases that were easy to code in the first place. Rust brings you nothing a c coder couldn't already do in c.... They haven't solved the enduring problems of computer science, they have simply kicked the can down the road
- smallstepforman 6mo agoThis is all well and dandy for some usage scenarios but breaks in others, eg. scene graphs and GUI's. A scene graph needs 2 mutable references, and has nothing to do with ownership. Same issue exists with GUI's. The pattern that Rust forces is to always request a reference, which incurs a performance penalty while retrieving the same reference again and again and again.
- TonyStr 6mo agoI am not familiar with scene graphs, but what is the problem with borrowing or refcounting? This article showed how you can have multiple mutable references in Rust, even multiple mutable references running in parallel threads.
- smallstepforman 6mo agoRef counting is for ownership, it doesnt convey intent. It kind of accidently works but is the wrong abstraction, especially in code bases where ownership is known.
- mattstir 6mo agoIf you're building data structures that have specific requirements and you know you'll implement it correctly, you can use raw pointers like `*mut T`. That's why they're in the language, for when you need to do something that the borrow checker can't verify and don't want the overhead of runtime borrow checks.
- john-h-k 6mo agoBranch predictor can predict these pretty damn well
- seanalltogether 6mo agoI've not really used Rust, and I can certainly appreciate the goal of the language. However this code bothers me, and I can't really articulate why. struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>>, parent: Option<Weak<RefCell<Node>>>>, // Weak breaks parent→child cycle } I understand its all boxes inside boxes inside boxes, but as an outsider it looks confusing mixing data type semantics with memory managment semantics.
- spoiler 6mo agoMemory _is_ data at the level Rust works. It just has a few abstractions in the stdlib that hide this. And the code you highlighted is taking steps in the "let's add abstraction" direction
- sysguest 6mo agoyeah it's too many generic depth... there ought to be a crate that handles "Vec<Rc<RefCell<..." part (maybe https://crates.io/crates/orx-concurrent-vec https://crates.io/crates/orx-concurrent-vec ? haven't tried it yet)
- simonask 6mo agoIt bothers me too, maybe for different reasons, as an experienced rustacean. Code like this is a world of pain (in any language, might I add, even though Rust is the only one that shows you why with the syntax). Internally shared ownership is a huge anti-pattern and rarely achieves what you’re actually trying to do. Graphs like these are much better represented by splitting the topology from the node data, and using indices or keys to form edges instead of (smart) pointers. You can use pointers, but then the correct choice is actually raw pointers and `unsafe`, with a safe whole-graph-level API for traversal and access. This is what Rust’s own collection types do internally.
- BiteCode_dev 6mo agoDo you have a simple example of how this is done correctly? I always heard that but comming from Python internal references would be the way to go so it's not natural to me.
- mattstir 6mo agoThis is a nice article! I think it introduces some concepts that Rust beginners might question ("what is `Copy` and what implements that?", "what's interior mutability? Doesn't that break the aliasing XOR mutability idea?"), but it gets the practical usage info across. > Every heap allocation has exactly one owner... This goes one step further, in that every value including on the stack has a single owner. That's why you can't pass the same `let a = [0; 10];` by value multiple times, even though that's an array on the stack. > Zero overhead — no extra indirection I think this is slightly misleading for borrowing, since a reference (`&`) is fundamentally a pointer that the compiler ensures upholds some special properties. So borrows come with a pointer deref when used, similarly to a `Box` or `Rc`, etc. I think the intention is to point out the heap allocations when using smart pointers which are definitely more expensive than `&`, but that doesn't really come across. Only other call-out is that I'm not sure what the various colours indicate throughout. They look really pretty, but I'm not sure when something becomes teal vs yellow vs orange vs purple.
- _alphageek 6mo agoI would also mention Rc/Arc is Cow<T> -it lets you borrow by default and only clone when you actually mutate. It sits in the middle of the spectrum andsaves a lot of allocations in code paths where most callers just read.
- bagol 6mo agoGood thing about rust is it forces you to write code in disciplined manner. I remember myself fighting those rules. Until it becomes natural to me and I can detect why the compiler rejected my code without reading the compiler error (for a simple one of course, but not syntax error). And that habit of carefully writing good code without slowing down carries over when I'm writing code in other languages.