How Ownership works in Rust

notes from: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html

the prerequisite: stacks and heaps and how they tie to ownership

stacks

Think of a stack of plates: when you add more plates, you put them on top of the pile, and when you need a plate, you take one off the top.

the stack stores values in the order it gets them and removes the values in the opposite order, Last In First Out (LIFO)

all data stored on the stack must have a known, fixed size

Data with an unknown size at compile time or a size that might change must be stored on the heap instead

heaps

like being seated at a restaurant, you state the number of people in your group, and the staff finds you an empty table (allocation) that fits everyone and leads you there. If someone comes late and wants to find you, they can ask where you've been seated (pointer) to find you

when you put data on a heap, you request a certain amount of space (at the time when you request it)

the memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of the location. this process is called allocating on the heap / allocating

because the pointer to the heeap is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer

pushing to a stack is faster than allocating on the heap

accessing data on a heap is slower than stacks because you have to follow a pointer to get there. comtemporary processors are faster if they jump around less in memory

comparison

stacksheaps
faster to push, no need to search for a place to store new dataslower to push, cause the allocator first needs to find a big enough space, and also perform bookkeeping to prep for next allocation
faster to access data, cause its always at the top of the stackaccessing data is slower, need to follow a pointer to get there
faaster cause it works on data that's close to other datadata is usually farther away from each other and processor has to follow pointers to get there

how they work together on rust

what ownership addresses

TLDR: the main purpose of ownership is to manage heap data

ownership rules

the cool bit

with a String type (mutable, growable piece of text at runtime, e.g. user input), we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. this means:

how different paradigms do it

non garbage collected languages (e.g. C)garbage collected languages (e.g. JS)ownership (e.g. rust)
requesting memorychar _str; str = (char _) malloc(15);String.fromCharCode(...[1,3,5])String::from("hello")
returning memoryfree(str); <-- manuallyin GC, the GC keeps track of and cleans up memory that isn't being used anymore (we don't need to think about it)memory is automatically returned once the variable that owns it goes out of scope
{
    let s = String::from("hello"); // s is valid from this point forward

    // do stuff with s
}                                  // this scope is now over, and s is no
                                    // longer valid

Note: In C++, this pattern of deallocating resources at the end of an item’s lifetime is sometimes called Resource Acquisition Is Initialization (RAII). The drop function in Rust will be familiar to you if you’ve used RAII patterns.

an aside on non gc languages and what can go wrong

So what are the errors that programmer make in languages like C that don't support garbage collection? I would guess not deallocating objects after they're not used anymore. But are these the only errors that we can make because of the lack of a garbage collector?

There are probably more. The point is: managing memory is tricky, and is best dealt with using some sort of tracking mechanism and allocating/freeing abstraction. As such, you might as well have that built into your language, so it can make it nice and easy for you. Manual memory management isn't the end of the world -- it's certainly doable -- but these days, unless you're writing real-time code, hardware drivers, or (maybe, possibly) the ultra-optimised core code of the latest game, then manual effort isn't worth it, except as an academic exercise.

TBC: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#ways-variables-and-data-interact-move

in rust, primitives work like primitives in all languages. they can be copied etc and reassigning them in rust, Strings (since they are not primitive) work differently

const thing1 = {
  foo: "bar",
};

const thing2 = thing1; // <-- SHALLOW COPY thing 2 is just a reference to the object created at thing1

const thing3 = {
  foo: "bar",
};

console.log("thing1 === thing2", thing2 === thing1); // true
console.log("thing1 === thing3", thing3 === thing1); // false

// but if you change things in thing2, thing1 will change too (usually unintended)

in rust, there is no shallow vs deep copy

let thing1 = String::from("hello");
let thing2 = thing1; // same instance, but uhm actually rust moves it (thing1 is no longer valid)

println!("comparing two cloned strings: {}", thing2 == thing1); // error, thing1 has moved

let thing3 = String::from("hello"); // different instance

but slightly different (notes in code above). thing1 becomes invalid after. trying to use thing1 after thing1 has been moved to thing2 raises an error similar to this:

borrow of moved value: `thing1`
value borrowed here after moverustcClick for full compiler diagnostic
main.rs(5, 18): value moved here
main.rs(4, 9): move occurs because `thing1` has type `String`, which does not implement the `Copy` trait

if we want to copy the data though, we must be explicit:

let thing1 = String::from("hello");
let thing2 = thing1.clone(); // same instance, but uhm actually rust moves it (thing1 is no longer valid)

println!("comparing two cloned strings: {}", thing2 == thing1); // true

here, have a gist: https://gist.github.com/panzerstadt/0cb1639e894d2d547de7ad16da6bbc29

Rust won’t let us annotate a type with Copy if the type, or any of its parts, has implemented the Drop trait.

what stuff in rust can implement copy?

in essense, stack-only data, for example:

the last one would probably look quite surprising, so here's an example:

let tup1 = (1, 2, false);
let tup2 = tup1;

println!("copying tuples that both consist of stack only data = ok: {}", tup1 == tup2); // OK, data is copied

let tup3 = (1, String::from("hello"));
let tup4 = tup3;

println!("copying tuples that have mixed stack-only and memory-allocated data = dame: {}", tup3 == tup4); // ERROR, tup3 is moved, not copied

lastly, passing a value to a function are similar to those when assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment does.

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.