Solid 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.
The 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.
I'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.
Rust 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.
This 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.
I understand its all boxes inside boxes inside boxes, but as an outsider it looks confusing mixing data type semantics with memory managment semantics.
This 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.
I 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.
Good 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.
12 comments
[ 2.4 ms ] story [ 36.0 ms ] threadEven memory locality of a arena doesn't provide benefit, because memory order does not mirror execution order.
In various forms, affine types, linear types, dependent types, effects, formal proofs.
The list is already rather long, D, Chapel, Swift, Linear Haskell, Ox, OCaml, Koka, Ada/SPARK, Mojo,...
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.
> 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.