Rust notes
InstallationEdit
Windows InstallEdit
Download and install[1]Edit
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe
Link.exe fixEdit
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 installEdit
curl https://sh.rustup.rs -sSf | sh
CodeEdit
Using statementsEdit
use std::io; use rand::Rng; use std::cmp::Ordering;
Function definitionEdit
fn main() { }
Write to consoleEdit
println!("Guess the number!");
Write to console with variable string interpolationEdit
println!("The secret number is: {secret_number}");
Define immutable string (defaults to immutable)Edit
let x = 5;
Define mutable stringEdit
let mut guess = String::new();
Define constantEdit
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
Read from console with error handlingEdit
io::stdin() .read_line(&mut guess) .expect("Failed to read line");
Cast string to unsigned 32 bit integer with error handlingEdit
let guess: u32 = guess.trim().parse().expect("Please type a number!");
Variable comparison and case optionsEdit
match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), }
Get random number with rangeEdit
let secret_number = rand::thread_rng().gen_range(1..=100);
Validating inputEdit
let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };
Variable shadowingEdit
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 typesEdit
IntegersEdit
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 pointEdit
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 operationsEdit
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; }
BooleanEdit
fn main() { let t = true; let f: bool = false; // with explicit type annotation }
CharEdit
fn main() { let c = 'z'; let z: char = 'Z'; // with explicit type annotation let heart_eyed_cat = '��'; }
TuplesEdit
fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); }
Tuple with deconstructionEdit
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; }
ArrayEdit
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 accessEdit
fn main() { let a = [1, 2, 3, 4, 5]; let first = a[0]; let second = a[1]; }
Functions with arguments / parametersEdit
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 ExpressionsEdit
• 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 argumentsEdit
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.
CommentsEdit
// this is a comment
Control flowEdit
ifEdit
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 breakingEdit
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}"); }
whileEdit
fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }
forEdit
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!!!"); }
StackEdit
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.
HeapEdit
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 RulesEdit
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 scopeEdit
StringsEdit
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.