Rust Memory Management
zee-nix.blogspot.com
zee-nix.blogspot.com
When Rust folks talk about "move", it is not just jargon about ownership. They actually mean move to a new place in RAM. If you could do a printf("%p", foo) before and after, it would be different. So obviously if there are other pointers to that object, they aren't going to be valid anymore. Figuring this out really helped me reason about what was allowed and what wasn't. Maybe it will help out someone else too. "Oh, you mean move!" :-)
That's not really true though. In an optimized build rustc will elide memcpys whenever possible, which it often is. "Move" in Rust really is about ownership.
Btw I have a saying: when you teach, at first you have to lie a little. You paint the broad strokes, then you refine, then refine again, etc. So it's a question of what is a helpful mental model. Knowing the optimization behavior is helpful for some things, but for figuring out valid memory behavior it's useful to pretend you don't know about that.
I hope he does one for lifetimes as I still find them confusing
Has this ever been proven to be a problem in practice, given that a program's stack is typically limited in size? Would the recommended workaround (if this was a problem) be to change variables to use heap instead?
Allocation more or less everything on the heap (i.e. using the stack only for calls and returns) is something that's done out of necessity in exclusively GC'd languages, like Java or Python.
I'm just wondering if the extra emphasis on stack allocation has created issues; this article doesn't cover the allocation of Strings or Vecs (wrapping them in RC and ARC boxes), and most other references on the net are a few years old now.
As in C++, these are small stack structures (a few words, IIRC it's 3 for Vec, and String is backed by a Vec) pointing to a heap-allocated buffer.
That was my first thought as well when I read that passage. Do you know why the author might have felt the need to state this specific in the context of Rust then?
Note that it only applies to local variables. If you have a primitive inside an object then it's allocated on the heap as part of the outer object.
It is occasionally a problem. For example, when writing my old emulator, I statically allocated the system's RAM (which has a fixed size) on the stack at first before thinking better of it and moving it to the heap. But such situations are uncommon.
I think the move to Rust should attract more developers to the Gnome ecosystem. The learning curve of C or Vala + GTK + GObject and related tech can be a bit steep.
As a matter of policy Rust doesn't try to figure out behavior of functions from their bodies, because that would mean changes in the implementation could accidentally break the API.
While in this example taking ownership is pointless, there are cases where it's meanigful: e.g. a method that closes a file handle would take ownership of the handle to ensure that the handle can't be used anywhere else after it's closed.
In Rust ownership allows mutability (if you own an object, and there are no other references to it, you can do whatever you want to it).
I understand not trying to look at function bodies but the mutability of a function seems to be strictly a property of it's type signature.
But I think what really bothers me is that whether a value is actually passed to a function by pointer or by copying it onto the stack is governed by whether it has the 'copy' trait - not something obvious to the casual reader of the program. Silently converting what looks like a pass by value call to an in-practice pass by reference based on a trait definition elsewhere in the code base just seems excessively magical to me and I'd be much happier if the compiler threw an error at
let answer = add_first_element(v1, v2);
because pass by value requires the copy trait which Vecs v1 and v2 don't have.
Closing a file requires ownership and is actually an RAII action: when the file object itself goes out of scope (which by definition means all extant references have been removed) the handle is closed.
> But I think what really bothers me is that whether a value is actually passed to a function by pointer or by copying it onto the stack is governed by whether it has the 'copy' trait - not something obvious to the casual reader of the program.
That's not correct. The Copy trait means it's semantically copied rather than moved so the original value can still be used, it does not change the technicalities of parameter passing which is always by-value (unless you're explicitly creating a reference) although things may then get optimised differently.
> Silently converting what looks like a pass by value call to an in-practice pass by reference
There's no such thing. Both Vecs are passed by value. Outside of method calls (which have some syntactic sugar) Rust requires references to be created explicitly.
> I'd be much happier if the compiler threw an error at
> let answer = add_first_element(v1, v2);
> because pass by value requires the copy trait which Vecs v1 and v2 don't have.
It has no reason to and it does not. `add_first_element` gets its argument by value, since Vec is not a Copy type v1 and v2 are thus moved into the function and not accessible anymore from outside it.
Ah, no wonder I misunderstood then. I'd forgotten that RAII was something Rust had and that feature does very nicely explain that design decision.
I suppose the reason a Vec doesn't implement Copy then is that its members might not, like a file handle?
That is correct, Vec has a pointer to a heap allocated buffer, meaning if Vec were Copy, you'd get two Vec structures for the same backing buffer and that's a recipe for disaster.
In Rust there is no distinction between mutable owned data and immutable owned data. There are only mutable/immutable references and bindings, but if you own the data, you can mutate it.
fn foo(v: Vec)
fn foo(mut v: Vec)
are the same function. `mut` is not part of the type. Even the first function can do `let mut v = v` and make immutable owned `v` a mutable owned one (since Rust can guarantee there are no other references to it, so the mutation is not observable from outside).It's different from `&mut Vec`, which is a distinct type, and it mutably borrows the Vec.
> If the file handle is just a integer
That's why in Rust such values are wrapped in "newtypes" that don't allow copying, e.g. `struct Handle(i32)`.
> pass by value call to an in-practice pass by reference
Passing ownership of the `Vec` doesn't copy the items.
Ownership/references and passing by value/reference are different concepts.
`Box<Foo>` is an owned value, but it's implemented as a regular pointer (`Foo* ` in C). `&[Foo]` is a reference, but it's actually a small struct passed by value (`struct Ref {size_t len; Foo *data}` in C).
The difference between a copy and a move is whether you can still use the old value, and this is checked in the type system.