An Overview of Memory Management in Rust
pcwalton.github.com
pcwalton.github.com
I recommend reading through to punchline at the end of the "References" section. That's a nice example of how elevating these memory management patterns to a first-class language constructs results in a design win over C and C++.
But the compiler does know about the built-in smart pointers' semantics in order to perform the reference analysis. For custom smart pointers, you usually have to delimit the scope of a reference yourself with a block. For example:
let p = ARC(1024); // atomically reference counted
do p.ref |r: &int| {
... do stuff with r ...
// The compiler ensures that r cannot leave this
// scope...
}
So custom smart pointers cause some rightward drift. Figuring out how to remove this rightward drift is something I would like to solve in the future, but it'll require some more thought. In any case, it's a case of code being a bit uglier, not a case of loss of flexibility, so building a couple of smart pointers into the language seemed like an acceptable worse-is-better solution for Rust 1.0.Seems to me that the only difference between a smart pointer and declaring a variable is data on the heap vs data on the stack and there's not much difference there?
So you often create some sort of communications structure, keep one end yourself, and hand the other end off to a task. The smart-ness keeps this memory from being copied, which would be slow, but keeps you safe at the same time. Like this:
use task::spawn;
use pipes::stream;
use pipes::Port;
use pipes::Chan;
fn main() {
let (port, chan): (Port<int>, Chan<int>) = stream();
do spawn |move chan| {
chan.send(10);
}
io::println(int::str(port.recv()));
}
More: http://www.rustforrubyists.com/book/chapter-07.html and http://www.rustforrubyists.com/book/chapter-08.html (I use the older-as-of-yesterday term 'owned pointer' rather than 'smart pointer.') fn make_string() -> ~str {
return ~"foo";
}
fn main() {
let string: ~str = make_string();
io::println(string);
} // deallocated here let a: ~Point = ~Point { x: 10, y: 20 };
let b: ~Point = ~Point { x: 20, y: 40 };
if (foo()) {
b = a;
}
println(a.x.to_str());The escape hatch is to use the generic "Cell<T>" type in the standard library, which gives you dynamic behavior much like C++11 move semantics. The nice thing about the static analysis, though, is that the checks don't have to be performed at runtime if the compiler can prove that the value is always moved at the end of the block.
fn main() {
let mut a = ~1;
let mut b = ~2;
if false { b = a; }
io::println(fmt!("%d", *a));
}
...the compiler will assume that `a` can be moved to `b` (although the precise analysis will reveal it can't) and therefore further use of `a` is unsafe. If you really want to assign `a` to `b`, you can do `b = copy a;` (warning: the syntax may change) to explicitly copy it without moving.The good news is that the Rust compiler does detect and clean up cycles of `@` smart pointers. At the moment, this is done at thread death only. But Graydon is nearly done with a new tracing garbage collector (written in Rust) that correctly detects and cleans up cycles.
You can create references using `&`, although I didn't show it in this overview.
I'd like to write one, but the whole IO part of the standard library is changing quickly, so I'm waiting till that is through.
I have a Rust buildpack sorta-kinda working almost....