# The Rust Programming Language - Ch4. Understanding Ownership ## Agenda Ownership, Reference, Slice. Note: All relevent to memory management ## References - https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html - https://course.rs/basic/ownership/index.html ## 4.1 What is Ownership ? ### Firstly before talk about Ownership... check out a C code snippet ```clike= int* foo() { char * s = (char *)malloc(100); int a = 100; return &a; } ``` :::spoiler What are the problems ? - :scream: Problem 1: the 100 bytes of memory pointed to by 's' are leaked. - :scream: Problem 2: returned pointer is dangling. #### :tada: Ownership in Rust will rescue !! :tada: (By demanding compile time checks that might drive you crazy :smiling_imp::smiling_imp: ) ::: ### What Is Ownership? #### Memory management... Typically there are 2 memory management paradigm: - By GC (Garbage Collection),by a runtime to keep track of all memory no longer being referenced,e.g. Java, Go - Good: safety - Bad: overhead, bulky runtime - Manually managed by programmer, all allocation and deallocation are explicit, e.g. C, C++ - Good: performance, thin runtime - Bad: error-prone Rust choose neither of above, but **Ownership**. Ownership is a set of rules to manage memory, and the rules are able to be checked **==at compile time==** :::info The Stack :cut_of_meat:(x) and the Heap (Super simplified, just for concept) This is virtual memory space of a running program. - Dynamic data with known size will be put on stack (LIFO). - Dynamic data with unknown size (in compile time) will be put on heap (dynamically allocated). ![](https://i.imgur.com/T6gqPqT.png =600x) ::: ### Ownership Rules - **Each value** in Rust has an owner. - There can only be **one owner at a time**. - When the owner goes **==out of scope==**, the value will be **==dropped==** (automatically). ### Example 1 To illustrate: - **Each value** in Rust has an owner. - There can only be **one owner at a time**. ```rust= /* Bind the String to "s1". * Owner of the String is now "s1", "s1" got the "Ownership" */ let s1 = String::from("hello"); let s2 = s1; //Tranfser ownership of String from "s1" to "s2" //Try to use "s1" println!("{}, world!", s1); ``` See what above program will get if compile it. ``` error[E0382]: use of moved value: `s1` --> src/main.rs:5:28 | 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{}, world!", s1); | ^^ value used here after move | = note: move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait ``` The "s1" no longer has ownership to the string (i.e. its value has been moved to "s2"), accessing "s1" will make compiler complain. ### Huh ? `Copy` ?? `move` ?? Note the String struct in memory actually looks like this: ![](https://i.imgur.com/MSaUFky.png =300x) - The content characters are stored in allocated heap memory, it could be very large (depends on runtime behavior, user input...) - ==Copy== all the value from "s1" to "s2" may take lots of time - Have to allocate a new chunk of memory, copy all the chars - Similar to concept of "deep copy" in other languages ![](https://i.imgur.com/yZeHplm.png =300x) - Actually what `let s2 = s1` does is "==Move==" - Just copy the 'ptr', 'len', 'capacity', much less overhead - Similar to concept of "shallow copy" in other languages - :boom: ==Then invalidate 's1'== ![](https://i.imgur.com/QiABqTH.png =300x) ### How to do if I really want to copy ? Note the clone is expensive. :money_with_wings::money_with_wings::money_with_wings: ```rust= let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); ``` ### Ok, then how about the ==\`Copy\` trait== ? Check this code snippet. And guess how compiler will warn ? ```rust= let x = 5; let y = x; println!("x = {}, y = {}", x, y); ``` Answer is.. it is legal. :flushed::flushed::flushed: ``` //output x = 5, y = 5 ``` That is because i32 type is simple enough and its size is known at compile time (so it lives on stack, instead of on heap). It takes no much cost to copy. Thus Rust allowed it to do copy instead of ownership transferring. Then... How compiler to know which types are valid for copy ? There is a special annotation called the ==Copy trait==: - If a type implements `Copy` trait, it will be still valid after the assignment. - (Actually, it performs ==copy== instead of ==move==) - `Copy` trait is ==exclusive== to `Drop` trait - Some most seen types that implements `Copy` trait - All the integer types, such as `u32`. - The Boolean type, `bool` - All the floating point types, e.g. `f64`. - The character type, `char`. - Tuples, if they only contain types that also implement Copy. For example, `(i32, i32)` implements Copy, but `(i32, String)` does not. ### Example 2 - Ownership and Functions To illustrate: - When the owner goes **==out of scope==**, the value will be **==dropped==** (automatically). - Definition of scope: https://doc.rust-lang.org/reference/destructors.html#drop-scopes ```rust!= 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. ``` ### Example 3 - Return Values and Scope ```rust! fn main() { let s1 = gives_ownership(); // gives_ownership moves its return // value into s1 let s2 = String::from("hello"); // s2 comes into scope let s3 = takes_and_gives_back(s2); // s2 is moved into // takes_and_gives_back, which also // moves its return value into s3 /* println!("{}", s2); -- if you do this, will get error[E0382]: borrow of moved value: `s2` */ } // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing // happens. s1 goes out of scope and is dropped. fn gives_ownership() -> String { // gives_ownership will move its // return value into the function // that calls it let some_string = String::from("yours"); // some_string comes into scope some_string // some_string is returned and moves out to the calling function // note: *no* drop here. } // This function takes a String and returns one fn takes_and_gives_back(a_string: String) -> String { // a_string comes into // scope a_string // a_string is returned and moves out to the calling function } ``` ### How rust destructors works - The "drop" is actually "run the destructor" - https://doc.rust-lang.org/reference/destructors.html ![](https://i.imgur.com/o2OPFqN.png) ## 4.2 References and Borrowing - A reference is like a pointer which stores address of its referencing target - Unlike a pointer, a reference is guaranteed to point to a valid value of a particular type for the life of that reference - Taking reference will no transfer the ownership, it's called "Borrowing" ```rust= fn main() { let s1 = String::from("hello"); //Note we take reference to s1 here and pass into func. let len = calculate_length(&s1); //Note s1 is still valid, since its ownership to String value still holden println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() //Note 's' is reference, thus no "drop" upon leaving scope } ``` ![](https://i.imgur.com/EzpoH3o.png) ### Mutable References - As variable, reference is immutable by default ```rust! fn main() { let s = String::from("hello"); change(&s); } fn change(some_string: &String) { //pub fn push_str(&mut self, string: &str) some_string.push_str(", world"); } //incurs error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference ``` - Mark `mut` to make it mutable ```rust fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); } ``` ### Reference Validity #### Only one mutable reference to certain value in the same scope ```rust fn main() { let mut s1 = String::from("hello"); //Note we take reference to s1 here let refs = &mut s1; //intended to change the s1 //(note the function signatue of 'clear': pub fn clear(&mut self)) s1.clear(); println!("s1: {} refs: {}.", s1, refs); } ``` ``` error[E0499]: cannot borrow `s1` as mutable more than once at a time --> src/main.rs:8:5 | 6 | let refs = &mut s1; | ------- first mutable borrow occurs here 7 | 8 | s1.clear(); | ^^^^^^^^^^ second mutable borrow occurs here ... 11 | println!("s1: {} refs: {}.", s1, refs); | ---- first borrow later used here ``` This prevents ==data races== in compile time - Two or more pointers access the same data at the same time. - At least one of the pointers is being used to write to the data. - There’s no mechanism being used to synchronize access to the data. #### Mutable reference is not allowed to be along with immutable references ```rust! let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem let r3 = &mut s; // BIG PROBLEM -> error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable println!("{}, {}, and {}", r1, r2, r3); ``` This prevents r1, r2 reads modified data, which might be unexpected. It's okay if mutable reference taken after last use of r1, r2. (The scope of r1, r2 ends at last time it is used) Why: Since it will no longer interfere any reading by immutable reference. ```rust let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem println!("{} and {}", r1, r2); // variables r1 and r2 will not be used after this point let r3 = &mut s; // no problem println!("{}", r3); ``` ### Dangling References Rust compiler will ensure reference is always valid. In other words, no chance for dangling reference to happen. ```rust fn dangle() -> &String { // dangle returns a reference to a String let s = String::from("hello"); // s is a new String &s // we return a reference to the String, s } // Here, s goes out of scope, and is dropped. Its memory goes away. // Danger! ``` ```! $ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0106]: missing lifetime specifier --> src/main.rs:5:16 | 5 | fn dangle() -> &String { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime | 5 | fn dangle() -> &'static String { | ~~~~~~~~ For more information about this error, try `rustc --explain E0106`. error: could not compile `ownership` due to previous error ``` ### Take away - At any given time, you can have either: - One mutable reference - Or any number of immutable references. - References must always be valid. ## 4.3 The Slice Type Before get into Slice, check this case out. #### Example: Find first word... without slice (bad practice) ```rust= fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() { let mut s = String::from("hello world"); let word = first_word(&s); // word will get the value 5 s.clear(); // this empties the String, making it equal to "" // word still has the value 5 here, but there's no more string that // we could meaningfully use the value 5 with. word is now totally invalid! } ``` (Note: [enumerate API doc.](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.enumerate)) Guess what will happen if we insist to print out the word after `s.clear()` ? Answer: Program will panic :grimacing::dizzy_face::sneezing_face: #### Check Out Slice - Slices references a contiguous sequence of elements in a collection. - Slice is a kind of reference, so it does not have ownership. Basic ```rust let s = String::from("hello world"); //&s[startIdx...postEndIdx], note postEndIdx is not included let hello = &s[0..5]; let world = &s[6..11]; ``` ![](https://i.imgur.com/UPQIvXN.png =300x) Variations ```rust= let s = String::from("hello"); //You can ommit the start index, following 2 lines are equivalant let slice = &s[0..2]; let slice = &s[..2]; //You can ommit the start index, lines 9 & 10 are equivalant let len = s.len(); let slice = &s[3..len]; let slice = &s[3..]; //You can ommit both start & end, following 2 lines are equivalant let slice = &s[0..len]; let slice = &s[..]; ``` :::info String slice range indices must occur at valid UTF-8 character boundaries. Otherwise your program will exit with an error. ::: #### Example: Now find first word... with slice ```rust //Note first_word() now returns a reference fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } ``` With slice it provides more concrete check of data validity, at compile time, and run time. ```rust! fn main() { let mut s = String::from("hello world"); let word = first_word(&s); //s.clear(); //if try to modify the string, compile error! println!("first word is:{}", word); //if try to access out-out-boundary slice, panic! //Error msg: thread 'main' panicked at 'byte index 6 is out of bounds of `hello`' //println!("6th letter in word is:{}", &word[3..6]); } ``` ### String literal is actually Slice ```rust let s = "Hello, world!"; ``` The type of s here is &str: it’s a slice pointing to that specific point of the binary. This is also why string literals are immutable; &str is an immutable reference. ### String Slices as Parameters ```rust //This accepts only &String fn first_word(s: &String) -> &str { ... } //This accepts both &String and &str fn first_word(s: &str) -> &str { ... } ``` ```rust fn first_word(s: &str) -> &str { ... } fn main() { let my_string = String::from("hello world"); // `first_word` works on slices of `String`s, whether partial or whole let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); // `first_word` also works on references to `String`s, which are equivalent // to whole slices of `String`s let word = first_word(&my_string); let my_string_literal = "hello world"; // `first_word` works on slices of string literals, whether partial or whole let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]); // Because string literals *are* string slices already, // this works too, without the slice syntax! let word = first_word(my_string_literal); } ``` ### Other Slices Slice also works on non-String collection types. e.g. The 'slice' below has the type &[i32] ```rust let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; assert_eq!(slice, &[2, 3]); ``` ## Wrapping-up Ownership and Reference rules ensure memory safty at compile time. ### Ownership Rules - **Each value** in Rust has an owner. - There can only be **one owner at a time**. - When the owner goes **==out of scope==**, the value will be **==dropped==** (automatically). ### Reference - Taking reference will **not transfer the ownership**, it’s called “**Borrowing**” - At any given time, you can have either: - One mutable reference - Or any number of immutable references. - References must always be valid. ### Slice - Slice is a **reference** to a range of elements in collection ### Get back to the bad C code at beginning... in Rust C ```clike= int* foo() { char * s = malloc(100); int a = 100; return &a; } ``` Rust ```clike= fn foo() -> &i32 { let s = String::from("new string"); let a = 100; &a //s will be dropped upon leaving function. } fn main() { let aa = foo(); println!("aa is: {}", aa); } ``` ```! error[E0106]: missing lifetime specifier --> src/main.rs:1:13 | 1 | fn foo() -> &i32 { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime | 1 | fn foo() -> &'static i32 { | +++++++ For more information about this error, try `rustc --explain E0106`. ``` - Problem 1: the 100 bytes of memory pointed to by 's' are leaked - Safe! :shield: Value of 's' will be dropped upon returning from function - Problem 2: returned pointer is dangling - Safe! :shield: Compiler won't let you do that