Skip to content

Instantly share code, notes, and snippets.

@rajeakshay
Last active May 29, 2023 18:13
Show Gist options
  • Save rajeakshay/a901497fe6a5b65a299b9bfaedcbf6fc to your computer and use it in GitHub Desktop.
Save rajeakshay/a901497fe6a5b65a299b9bfaedcbf6fc to your computer and use it in GitHub Desktop.
Notes from 'The Rust Programming Language' at https://rust-book.cs.brown.edu/

DISCLAIMER: These notes from 'The Rust Programming Language' book are my own and meant for my personal reference. They are not endorsed by the Rust Foundation. Visit https://www.rust-lang.org/ for the official material for learning Rust.

Installing Rust

  1. Install Rust from https://rustup.rs/.
  2. Check installation with -
    rustc --version
    
    OR
    cargo --version
    
  3. Update installation with -
    rustup update
    
  4. Uninstall Rust with -
    rustup self uninstall
    

Compiling Rust programs

  1. Compile simple programs with rustc and execute directly.

    welcome.rs

    fn main() {
        println!("Welcome to Rust!");
    }
    $ rustc welcome.rs && ./welcome
    Welcome to Rust!
    
  2. Compile projects with cargo build system cum package manager.

    • Cargo project structure looks like -
      src/main.rs
      Cargo.toml
      
    • Create a new project with -
      cargo new <project>
      
    • Quickly check that a project compiles without producing an executable -
      cargo check
      
    • Produce a debug build and execute it immediately -
      cargo run
      
    • Produce a release build -
      cargo build --release
      
  3. Try out simple programs at - https://play.rust-lang.org

Variables

// Constants can have any scope including global scope;
const ONE_HOUR_IN_SECONDS: u32 = 60 * 60;

fn main() {
    // Immutable; Can't be reassigned; Has function scope;
    let i = 3;
    
    println!("i: {i}");  // i: 3

    // Mutable; Can be reassigned; Has function scope;
    let mut j = 5;
    j = 6;
    
    println!("j: {j}");  // j: 6

    // Has function scope; Can't use 'mut';
    const ONE_MINUTE_IN_SECONDS: u32 = 60;

    println!("1 min in seconds: {ONE_MINUTE_IN_SECONDS}");  // 1 min in seconds: 60
    println!("1 hour in seconds: {ONE_HOUR_IN_SECONDS}");  // 1 hour in seconds: 3600

    let x = 5;
    let x = x + 1;  // Shadowing another variable of the same name;

    println!("x: {x}");  // x: 6

    let y = "   ";  // 'y' is a string literal;
    let y = y.len();  // 'y' becomes an integer type;

    println!("y: {y}");  // y: 3

    let mut z: u32 = 1;
    {
        let mut z = z;  // Shadowing in the inner scope;
        z += 2;
    
        println!("z: {z}");  // z: 3
    }
    
    println!("z: {z}");  // z: 1
}

Data Types

1. Scalar Types

fn main() {
    // Integer Types;
    // Signed variants can store -2^(n-1) to (2^(n-1))-1 inclusive;
    // Unsigned variants can store 0 to (2^n)-1 inclusive;
    // n can be 8, 16, 32, 64, or 128;
    
    let a: i8 = 127;
    let b: u8 = 255;
    
    println!("a: {}, b: {}", a, b);  // a: 127, b: 255
    
    // isize and usize types depend on the arch and are used when indexing a collection;
    
    // Integer Literals;
    let i = 57u8;  // u8;
    let j = 98_222;  // i32 (default); Decimal;
    let k = 0xff;  // Hex;
    let l = 0o77;  // Octal;
    let m = 0b1111_0000;  // Binary;
    let n = b'A';  // Byte (u8 only);
    
    println!("i: {}, j: {}, k: {}, l: {}, m: {}, n: {}", i, j, k, l, m, n);  // i: 57, j: 98222, k: 255, l: 63, m: 240, n: 65
    
    // Floating-Point Types;
    let x = 2.0;  // f64 (default); double-precision float;
    let y: f32 = 3.0;  // f32; single-precision float;
    
    println!("x: {}, y: {}", x, y);  // x: 2, y: 3
    
    // Boolean Type;
    let u = true;
    let v: bool = false;
    
    println!("u: {}, v: {}", u, v);  // u: true, v: false
    
    // Char Type;
    // 'char' type is four bytes in size and represents a Unicode Scalar Value;
    let o: char = 'Z';
    
    println!("o: {o}");  // o: Z
}

2. Complex Types

fn main() {
  // Tuple Type;
  let t: (i32, f64, u8) = (500, 6.4, 1);
  let (a, b, c) = t;  // Destructuring;
  
  println!("a: {}, b: {}, c: {}", a, b, c);  // a: 500, b: 6.4, c: 1
  
  let x = (900, 3.5, 'a');
  let y = x.2;  // Accessing tuple elements using a zero-based index;
  
  println!("x: {:?}, y: {}", x, y);  // x: (900, 3.5, 'a'), y: a
  
  let u = ();  // Empty tuple is a 'unit' and is the implicit return value of a function;
  
  println!("u: {:?}", u);  // u: ()
  
  // Array Type;
  let p: [i32; 5] = [1,2,3,4,5];
  let q = [0; 7];  // Create an array with 7 zeros
  
  println!("p: {:?}, q: {:?}", p, q);  // p: [1, 2, 3, 4, 5], q: [0, 0, 0, 0, 0, 0, 0]
  
  let p1 = p[0];  // Accessing tuple elements using a zero-based index;
  let q3 = q[2];
  
  println!("p1: {}, q3: {}", p1, q3);  // p1: 1, q3: 0
  
}

Functions

// A curly brace block like { /* ... */ } is an expression that can contain statements and
// also defines a syntactic scope for 'let'-bindings inside it.

// Expressions can have results but statements can't.

// Statements end with a semi-colon and can't be used for implicit returns from a function.

fn f(x: i32) -> i32 {
    x + 1
}

fn main() {
    println!("{}", f({
        let y = 1;
        y + 1
    }));  // 3
}

Control Flow

use std::cmp::Ordering;

fn main() {
    // if-else expressions
    let num1 = 3;
    let num2 = 6;
    if num1 > num2 {
        println!("{num1} is greater than {num2}");   
    } else if num2 > num1 {
        println!("{num2} is greater than {num1}");
    } else {
        println!("{num1} is equal to {num2}");
    }
    
    // Using if in a let statement
    let num3 = 7;
    let what = if num3 % 2 == 0 { "even" } else { "odd" };  // if & else arms should have compatible types
    
    println!("{} is {}", num3, what);  // 7 is odd
    
    // Using match instead of an if-else ladder
    let num4 = 43;
    let num5 = 39;
    match num4.cmp(&num5) {
        Ordering::Less => println!("{num5} is greater than {num4}"),
        Ordering::Greater => println!("{num4} is greater than {num5}"),
        Ordering::Equal => println!("{num4} is equal to {num5}"),
    }
    
    // Repetition with loop
    let mut counter = 0;
    
    println!("Before loop starts, counter: {counter}");  // Before loop starts, counter: 0
    
    loop {
        if counter == 10 {
            break;
        }
        counter += 1;
    }
    
    println!("After loop ends, counter: {counter}");  // After loop ends, counter: 10
    
    // Conditional loops with while
    let mut countdown = 10;
    
    println!("Before while starts, countdown: {countdown}");  // Before while starts, countdown: 10
    
    while countdown != 0 {
        countdown -= 1;
    }
    
    println!("After while ends, countdown: {countdown}");  // After while ends, countdown: 0
    
    // Looping over items in a collection with for
    let fibonacci = [1, 2, 3, 5, 8];
    
    for element in fibonacci {
        println!("{element}");
    }
    
    // Using a Range with for
    for item in (1..5).rev() {
        println!("{item}");
    }
    
}

Ownership

// f takes ownership of argument i
fn f(i: i32) -> i32 {
    i * 2
}

// g acts on a reference s without assuming ownership
fn g(s: &String) -> usize {
    s.len()
}

// h acts on a mutable reference n without assuming ownership
fn h(n: &mut i32) {
    *n += 1;
}

fn main() {
    // Moving ownership
    let x = 7;
    let mut y = x;  // x is moved to y and can no longer be used after this line
    y += 6;
    
    println!("y: {y}");  // y: 13
    
    let n = 4;
    let z = f(n);  // n is moved to f and can no longer be used after this line
    
    println!("z: {z}");  // z: 8
    
    // References are non-owning pointers
    let hello = String::from("hello");
    let length = g(&hello);  // hello can still be used after this line
    
    println!("hello: {}, length: {}", hello, length);  // hello: hello, length: 5
    
    // Dereferencing a pointer
    let mut hval: Box<i32> = Box::new(1);
    let a: i32 = *hval;  // *hval reads the heap value
    *hval += 1;  // *hval on the LHS modifies the heap value
    
    println!("hval: {hval}, a: {a}");  // hval: 2, a: 1
    
    let ref1: &Box<i32> = &hval;  // ref1 points to hval on the stack
    let b: i32 = **ref1;  // 2 derefences to get to the heap value
    
    println!("ref1: {ref1}, b: {b}");  // ref1: 2, b: 2
    
    let ref2: &i32 = &*hval;  // ref2 points to the heap value directly
    let c: i32 = *ref2;  // 1 derefence to get to the heap value
    
    println!("ref2: {ref2}, c: {c}");  // ref2: 2, c: 2
    
    let ref3: Box<&Box<i32>> = Box::new(&hval);  // ref3 points to a stack reference to a heap pointer
    let d: i32 = ***ref3;  // 3 dereferences to get to the heap value
    
    println!("ref3: {ref3}, d: {d}");  // ref3: 2, d: 2
    
    // Mutable references
    let mut num = 1;
    h(&mut num);  // Pass a mutable reference to modify the value on stack
    
    println!("num: {num}");  // num: 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment