Variables

By default, variables are immutable, need to use mut. Static typing allow type inference, do not pass the type :<type>, and let rust infer it.

const use the compile-time constants.

Shadowing

Declare a variable with the same name as a previous variable. The first variable is shadowed by the second on this case.

fn main() {
    let x = 5;
    let x = x + 1;

    {
        let x = 100;
        println!("inner scope: {x}");
        let x = true;
        println!("shadowed in inner scope: {x}");
    }
    
    println!("outer scope {x}");
}

Memory management

Rust offers full control AND safety via compile time enforcement of correct memory management. Explicit Ownership concept.

Refer to mem management section from the tutorial.

Ownership

Binding have a scope where they are valid. A set of rules that govern how a Rust program manages memory. All programs have to manage the way they use memory while running. Memory is managed through a system of ownership with a set of rules that the compiler checks.

  • Each value has an owner
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value is dropped.

Move

fn main() {
    let s1 = String::from("hello");
    let s2: String = s1;
}

Data is moved from one s1 and s1 is not accessible anymore - This is a problem: when s2 and s1 go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities. Rust doesn’t need to free anything when s1 goes out of scope. when accessing it raises an error:

value is borrowed here after move.

Transfer ownership happens on function parameters as well. Another way to interect is via s1.close().

Struct must use #[derive(Copy, Clone, Debug)] annotation.

For functions is not allowed to use s after the function ownership taken

fn takes_ownership(s :String) {
    println!("on function: {}", s)
}

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

    takes_ownership(s);             // s's value moves into the function...
    println!("outside function: {}", s)
}

Borrowing and lifetimes

Passing references (&) makes the data still being owner by some other variable. function(&s1), allows to refer the value of s1 but do not own it. The caller retains ownership of the inputs.


fn main() {
    let p1 = String::from("hello");
    let f1;
    
    {
        let p2 :String = String::from("hello 2");
        f1 = function(&p1.as_str(), &p2.as_str());
    }
    
    println!("{f1} {p1}");
}

// returns with p2 lifetime if p2 has 'a.
// forcing the lifetime for the p1 variable allows the
// return.
fn function<'a>(p1 :&'a str, p2 :&str) -> &'a str{ ;; 
    println!("function {p1} {p2}");
    return p1
}

These borrowed values has a lifetime - ensure that references are valid as long as we need them to be. &'a String means “a borrowed String which is valid for at least the lifetime a`

Reference:

https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html?highlight=lifetime#lifetime-elision

Exercises

  • Designing a library
impl Library {
    fn new(books :Vec<Book>) -> Library {
        Library{ books }
    }

    fn len(&self) -> usize {
        self.books.len()
    }

    fn is_empty(&self) -> bool {
        self.books.is_empty()
    }

    fn add_book(&mut self, book: Book) {
        self.books.push(book)
    }

    fn print_books(&self) {
        for b in self.books.iter() {
            println!("{}", b)
        }
    }

    fn oldest_book(&mut self) -> Option<&Book> {
        self.books.sort_by(|a, b| a.year.cmp(&b.year));
        self.books.first()
    }
}

fn main() {
    // This shows the desired behavior. Uncomment the code below and
    // implement the missing methods. You will need to update the
    // method signatures, including the "self" parameter!
    let books :Vec<Book> = vec!();
    let mut library = Library::new(books);

    println!("Our library is empty: {}", library.is_empty());

    library.add_book(Book::new("Lord of the Rings", 1954));
    library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));

    library.print_books();

    match library.oldest_book() {
        Some(book) => println!("My oldest book is {book}"),
        None => println!("My library is empty!"),
    }

   println!("Our library has {} books", library.len());
}