Rust study notes
Rust Programming Language Introduction Tutorial Course Notes
Reference textbook: The Rust Programming Language (by Steve Klabnik and Carol Nichols, with contributions from the Rust Community)
Lecture 3 Common Programming Concepts
fn main() {<!-- --> // Variables and Mutability let mut x = 5; // mut means mutable println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); // Constants const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; // all caps with underscores println!("The value of THREE_HOURS_IN_SECONDS is: {THREE_HOURS_IN_SECONDS}"); // Shadowing let y = 5; println!("The value of y is: {y}"); let y = y + 1; //shadowing // this is allowed because we are creating a new variable println!("The value of y is: {y}"); // Shadowing vs Mutability let spaces = " "; let spaces = spaces.len(); // this is allowed because we are creating a new variable println!("The value of spaces is: {spaces}"); //Data Types let guess: u32 = "42".parse().expect("Not a number!"); // type annotation println!("The value of guess is: {guess}"); let m = 57u8; // u8 println!("The value of m is: {m}"); let n = 1_000_000; // i32 println!("The value of n is: {n}"); let x = 2.0; // f64 let y: f32 = 3.0; // f32 println!("The value of x is: {x}"); println!("The value of y is: {y}"); //addition let sum = 5 + 10; println!("The value of sum is: {sum}"); //subtraction let difference = 95.5 - 4.3; println!("The value of difference is: {difference}"); // multiplication let product = 4 * 30; println!("The value of product is: {product}"); //division let quotient = 56.7 / 32.2; let truncated = -5 / 3; // Results in -1 println!("The value of quotient is: {quotient}"); println!("The value of truncated is: {truncated}"); // remainder let remainder = 43 % 5; println!("The value of remainder is: {remainder}"); //boolean let t = true; let f: bool = false; // with explicit type annotation println!("The value of t is: {t}"); println!("The value of f is: {f}"); // character let c = 'z'; let z: char = '?'; // with explicit type annotation let heart_eyed_cat = ''; println!("The value of c is: {c}"); println!("The value of z is: {z}"); println!("The value of heart_eyed_cat is: {heart_eyed_cat}"); // Compound Types // Tuple let tup: (i32, f64, u8) = (500, 6.4, 1); // type annotation println!("The value of tup is: {:?}", tup); let (x, y, z) = tup; // destructuring println!("The value of x is: {x}"); println!("The value of y is: {y}"); println!("The value of z is: {z}"); let five_hundred = tup.0; // accessing tuple elements println!("The value of five_hundred is: {five_hundred}"); let six_point_four = tup.1; println!("The value of six_point_four is: {six_point_four}"); let one = tup.2; println!("The value of one is: {one}"); //Array //saved on stack // fixed length and same type let a = [1, 2, 3, 4, 5]; // array println!("The value of a is: {:?}", a); let a = [3; 5]; // [3, 3, 3, 3, 3] // [element; size] let first = a[0]; let second = a[1]; println!("The value of first is: {first}"); println!("The value of second is: {second}"); // Functions another_function(5); println!("The value of add_five(10) is: {}", add_five(10)); //Control Flow // if-else flow let number = 6; if number % 4 == 0 {<!-- --> println!("number is divisible by 4"); } else if number % 3 == 0 {<!-- --> println!("number is divisible by 3"); } else if number % 2 == 0 {<!-- --> println!("number is divisible by 2"); } else {<!-- --> println!("number is not divisible by 4, 3, or 2"); } let condition = true; let number = if condition {<!-- --> 5 } else {<!-- --> 6 }; println!("The value of number is: {number}"); // loop flow let mut counter = 0; let result = loop {<!-- --> counter + = 1; if counter == 10 {<!-- --> break counter * 2; // break with value }// no semicolon here };// semicolon here println!("The value of result is: {result}"); // while flow let mut number = 3; while number != 0 {<!-- --> println!("{}!", number); number -= 1; }// no semicolon here println!("LIFTOFF!!!"); // for flow let a = [10, 20, 30, 40, 50]; for element in a.iter() {<!-- -->// iter() returns each element in a collection println!("the value is: {element}"); }// no semicolon here for number in 1..4 {<!-- -->// range [1, 4) = [1, 2, 3] println!("{}!", number); } for number in (1..4).rev() {<!-- -->// reverse println!("{}!", number); } } fn another_function(x: i32) {<!-- -->// type annotation println!("Another function."); println!("The value of x is: {x}"); } fn add_five(x: i32) -> i32 {<!-- --> 5 + x // no semicolon means return }
Lecture 4 Understanding Ownership
fn main() {<!-- --> //Ownership is a set of rules that govern how a Rust program manages memory. //These rules are checked at compile time. //Ownership is Rust's most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector. //All programs have to manage the way they use a computer's memory while running. //Stack and Heap //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. //The main purpose of ownership is to manage heap data. //Ownership Rules //1. Each value in Rust has a variable that’s called its owner. //2. There can only be one owner at a time. //3. When the owner goes out of scope, the value will be dropped. //Variable Scope let mut s = String::from("hello");//The String type is allocated on the heap. s.push_str(", world!"); // push_str() appends a literal to a String println!("{}", s); // This will print `hello, world!` //Memory and Allocation //With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means: //1. The memory must be requested from the memory allocator at runtime. //2. We need a way of returning this memory to the allocator when we’re done with our String. //Rust takes a different path: //the memory is automatically returned once the variable that owns it goes out of scope. //Move let x = 5; let y = x; //Stack-Only Data: Copy println!("x = {}, y = {}", x, y);//This works because integers are simple values with a known, fixed size, and these two 5 values are pushed onto the stack. let s1 = String::from("hello"); let s2 = s1; //println!("{}, world!", s1);//This will throw an error because Rust considers s1 to no longer be valid and, therefore, Rust doesn't need to free anything when s1 goes out of scope. println!("{}, world!", s2);//This will work because s2 is the new owner of the String data. //Clone let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);//This will work because s1.clone() creates a deep copy of the String data, not just a copy of the stack pointer . //Ownership and Functions 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 afterwards. //println!("{}", s);//This will throw an error because s is no longer valid. println!("{}", x);//This will work because x is still valid. //Return Values and Scope 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!("s1 = {}, s3 = {}", s1, s3);//Here, s3 goes out of scope and is dropped. s2 goes out of scope but was moved, so nothing happens. s1 goes out of scope and is dropped. //println!("{}", s2);//This will throw an error because s2 is no longer valid. //References and Borrowing let s1 = String::from("hello"); let len = calculate_length( & amp;s1);//The & amp;s1 syntax lets us create a reference that refers to the value of s1 but does not own it. println!("The length of '{}' is {}.", s1, len);//This will work because s1 is still valid. //Mutable References let mut s = String::from("hello"); change( & amp;mut s);//We can have only one mutable reference to a particular piece of data in a particular scope. println!("{}", s);//This will work because s is still valid. let mut s = String::from("hello"); let r1 = &mut s; //let r2 = & amp;mut s;//This will throw an error because we can have only one mutable reference to a particular piece of data in a particular scope. //println!("{}, {}", r1, r2); println!("{}", r1); let mut s = String::from("hello"); {<!-- --> let r1 = &mut s; println!("{}", r1); } // r1 goes out of scope here, so we can make a new reference with no problems. let r2 = &mut s; println!("{}", r2); let s = String::from("hello"); let r1 = & amp;s; // no problem let r2 = & amp;s; // no problem //let r3 = & amp;mut s; // BIG PROBLEM //This will throw an error because we can have either one mutable reference or any number of immutable references. println!("{}, {}", r1, r2); //Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used. let mut s = String::from("hello"); let r1 = & amp;s; // no problem let r2 = & amp;s; // no problem println!("{} and {}", r1, r2); // variables r1 and r2 will not be used after this point let r3 = & amp;mut s; // no problem println!("{}", r3); //Dangling References let reference_to_nothing = dangle(); println!("{}", reference_to_nothing); //The Rules of References //1. At any given time, you can have either one mutable reference or any number of immutable references. //2. References must always be valid. //The Slice Type //Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership. //Find the first word in a string let s = String::from("hello world"); //let wordIndex = first_word( & amp;s); let first = first_word( & amp;s); //s.clear();//This will throw an error because we have an immutable reference to s. println!("the first word is: {}", first); let hello = & amp;s[..5]; //[starting_index..ending_index) = [0..5) = [0, 1, 2, 3, 4] let world = & amp;s[6..]; //[starting_index..ending_index) = [6..11) = [6, 7, 8, 9, 10] println!("{} {}", hello, world); //Other Slices let a = [1, 2, 3, 4, 5]; let slice = & amp;a[1..3];//[starting_index..ending_index) = [1..3) = [1, 2] assert_eq!(slice, & amp;[2, 3]);//The assert_eq! macro tests whether two expressions are equal to each other; if they are, nothing happens, and if they aren't, the macro prints the two expressions and panics. } 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. fn gives_ownership() -> String {<!-- --> // gives_ownership will move its // return value into the function // that calls it. let some_string = String::from("hello"); // some_string comes into scope. some_string // some_string is returned and // moves out to the calling // function. } 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. } fn calculate_length(s: & amp;String) -> usize {<!-- -->//We call having references as function parameters borrowing. s.len() }//Here, s goes out of scope. But because it does not have ownership of what it refers to, nothing happens. fn change(some_string: & amp;mut String) {<!-- --> some_string.push_str(", world"); } // fn dangle() -> & amp;String { //This will throw an error because we are trying to return a reference to a String that is created inside the function. // let s = String::from("hello"); // &s // }//Here, s goes out of scope, and is dropped. Its memory goes away. Danger! fn dangle() -> String {<!-- --> let s = String::from("hello"); s } // fn first_word(s: & amp;String) -> usize {<!-- --> // let bytes = s.as_bytes(); // for (i, & amp;item) in bytes.iter().enumerate() {<!-- --> // if item == b' ' {<!-- --> // return i; // } // } //s.len() // } fn first_word(s: & amp;String) -> & amp;str {<!-- -->//We can use & amp;str as the type of the slice parameter to make it clear that the keys function will return slices of String values rather than whole String values. let bytes = s.as_bytes(); for (i, & amp;item) in bytes.iter().enumerate() {<!-- -->//The enumerate method returns a tuple consisting of the index and the reference to the element. if item == b' ' {<!-- --> return &s[0..i]; } } &s[..] } //1. fn first_word(s: & amp;String) -> & amp;str //2. fn first_word(s: & amp;str) -> & amp;str //A more experienced Rustacean would write the signature shown in type 2 instead because it allows us to use the same function on both & &String values and & &str values.