Rust notes
Installation
Windows Install
Download and install[1]
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe
Link.exe fix
https://visualstudio.microsoft.com/visual-cpp-build-tools/ and it will download a Visual Studio Installer.
Run it as Administrator, then make sure you download all three things listed below in the screenshot, versions don't matter, just try get latest ones.
Hit Install. May need to reboot computer. Definitely need a new shell to test.
Linux install
curl https://sh.rustup.rs -sSf | sh
Code
Using statements
use std::io; use rand::Rng; use std::cmp::Ordering;
Function definition
fn main() { }
Write to console
println!("Guess the number!");
Write to console with variable string interpolation
println!("The secret number is: {secret_number}");
Define immutable string (defaults to immutable)
let x = 5;
Define mutable string
let mut guess = String::new();
Define constant
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
Read from console with error handling
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
Cast string to unsigned 32 bit integer with error handling
let guess: u32 = guess.trim().parse().expect("Please type a number!");
Variable comparison and case options
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
Get random number with range
let secret_number = rand::thread_rng().gen_range(1..=100);
Validating input
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
Variable shadowing
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
println!("The value of x is: {x}");
}
println!("The value of x is: {x}");
}Variable is declared as 5. This is overshadowed by its redefinition to 6. It is shadowed again by the inner block where its value is 12, but this shadowing is only in scope for the inner block. Once this goes out of scope, it returns back to 6. Shadowing can be used to redefine a type also (i.e. name reuse) if it is mutable.
let spaces = " "; let spaces = spaces.len();
Variable types
Integers
| Length | Signed | Unsigned | Values |
| 8-bit | i8 | u8 | 256 |
| 16-bit | i16 | u16 | 65536 |
| 32-bit | i32 | u32 | 4.29E+09 |
| 64-bit | i64 | u64 | 1.84E+19 |
| 128-bit | i128 | u128 | 3.4E+38 |
| arch | isize | usize |
Arch is the architecture type. You can write integer literals in any of the forms shown in Table 3-2. Note that number literals that can be multiple numeric types allow a type suffix, such as 57u8 , to designatetype. Number literals can also use _ as a visual separator to make the number easier to read, such as 1_000 , which will have the same value as if you had specified 1000 .
| Number literals | Example |
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
| Byte ( u8 only) | b'A' |
Floating point
Rust's floating-point types are f32 and f64.
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}Floating-point numbers are represented according to the IEEE-754 standard. The f32 type is a single-precision float, and f64 has double precision.
Arithmetic operations
fn main() {
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1
// remainder
let remainder = 43 % 5;
}
Boolean
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
Char
fn main() {
let c = 'z';
let z: char = 'Z'; // with explicit type annotation
let heart_eyed_cat = '��';
}
Tuples
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
Tuple with deconstruction
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
}Tuple accessing
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
Array
fn main() {
let a = [1, 2, 3, 4, 5];
}let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
let a: [i32; 5] = [1, 2, 3, 4, 5];
Initialize an array to contain the same value for each element by specifying the initial value, followed by a semicolon, and then the length of the array in square brackets:
let a = [3; 5];
Array access
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
Functions with arguments / parameters
fn another_function(x: i32) {
println!("The value of x is: {x}");
}fn main() {
print_beled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
Statements and Expressions
• Statements are instructions that perform some action and do not return a value.
• Expressions evaluate to a resultant value.
fn main() {
let y = {
let x = 3;
x + 1
};
}
println!("The value of y is: {y}");
Functions with return and arguments
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1
}Note - notice that the last line above does not end with a semicolon. Adding one will cause a problem.
Comments
// this is a comment
Control flow
if
fn main() {
let number = 3;
if number != 0 {
println!("number was something other than zero");
}
}fn main() {
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 number = if condition { 5 } else { 6 };
loops and breaking
loop{
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}Breaking out of nested loops
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
while
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
for
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}
Stack
Both the stack and the heap are parts of memory but they are structured in different ways. The stack stores values in the order it gets them and removes the values in the opposite order. last in, first out. Adding data is called pushing and removing data is called popping
All data stored on the stack must have a known, fixed size.
Heap
Data is stored on the heap by requesting a certain amount of space. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap. Because the pointer to the heap is a known, fixed size, the pointer can be stored on the stack, but to access the actual data, the pointer must be followed.
Pushing / popping the stack is faster than allocating on the heap.
Ownership Rules
Ownership rules:
- Each value in Rust has an owner
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
Variable scope
Strings
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);The string "hello" is placed on the heap and the metadata i.e. pointer is stored on the stack. When the second line is executed, the stack data is changed and s2 becomes the owner of the string so s1 becomes invalid. This is like a shallow copy (like a 'by ref') but because of the invalidation it is known as a move. A deep copy, where the heap actual "hello" is duplicated also, use clone.
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);Objects stored on the stack can be copied without the above detail where clone and shallow copy are identical
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);• All the integer types
• The Boolean type, bool
• All the floating-point types
• The character type, char
• Tuples, if they only contain types that also implement Copy, eg (i32, i32) implements Copy
fn main() {
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 afterward
} // Here, x goes out of scope, then s. But because s's value was moved, nothing special happens.
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.