Rust2 Common Programming Concepts & Understanding Ownership

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.