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.
- Install Rust from https://rustup.rs/.
- Check installation with -
ORrustc --version
cargo --version
- Update installation with -
rustup update
- Uninstall Rust with -
rustup self uninstall
-
Compile simple programs with
rustc
and execute directly.welcome.rs
fn main() { println!("Welcome to Rust!"); }
$ rustc welcome.rs && ./welcome Welcome to Rust!
-
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
- Cargo project structure looks like -
-
Try out simple programs at - https://play.rust-lang.org
// 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
}
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
}
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
}
// 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
}
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}");
}
}
// 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
}