Enjoy Day 1
Glad to see you here! Let's Rust
!
💡 for more examples see 👉 rust-by-example
Variables, println, assert_eq
fn main() { // Define immutable variable. let count = 0; // {} mean param_0. println!("1. count = {}", count); // Define mutable variable. let mut count = 1; // So we can change it. count += 1; // {0} mean param_0. // {1} mean param_1. // ╭────────────────╮ println!("2. {0} = {1:#?}", "count", count); // ╰──────────────────╯ // # mean pretty print. // ? mean debug. // Let's make some condition. if count == 2 { // String literal {count} mean variable count for Display. println!("3. count = {count}"); } // Assert that count is equal 2. assert_eq!(count, 2); // As base 16 hexadecimal by adding 👇. println!("4. count = {count} = 0x{count:x}"); }
💡 More
println
pattern 👉 here
for, while, loop, break
fn main() { // 👇 Mutable so we change the value later. let mut count = 0; // This .. 👇 mean range i from 0 to 7. for _i in 0..8 { // _i mean we won't use i count += 1; } println!("1. count = {count}"); // This .. 👇 mean range i from 0 to 8. for i in 0..=8 { count += i; } println!("2. count = {count}", count = count); // 👇 This is how we loop element (e). for e in ["a","b","c"] { println!("3. {e}"); } // 👇 This is index (i) can be use by 👇 call enumerate fn. for (i, e) in ["a","b","c"].iter().enumerate() { println!("4. {i} = {e}"); } // while while count < 50 { count += 1; } println!("5. count = {0}", count); // loop loop { count += 1; if count >= 100 { break; } } println!("6. count = {}", count); // loop and break 'outer: loop { count += 1; // Break at 200 if count >= 200 { // Never reach here because 👇. break; } else { // Inner loop loop { count += 1; // Because this will break first. if count >= 150 { break 'outer; } } } } println!("7. count = {}", count); }
fn, const, static, return, format
// We previously use a lot of "count", let's DRY it as a constant. const COUNT: &str = "count"; // Say hi to referenced string slice &str // And maybe we want static footgun 💥 that can mutate. static mut total: u32 = 0; // Define "add" as a function fn add(a: i32, b: i32) -> i32 { // i32 = integer 32 a + b // This mean return a + b, hence no semicolon ; } fn main() { // We can use assert instead of assert_eq for test. assert!(add(1, 2) == 3); // Try use COUNT with format! let result = format!("{COUNT} = {}", add(1, 9)); println!("1. {result}"); // We will need unsafe to mutate static (fyi: bad practice). unsafe { // Try mutate and 👇 cast i32 to u32 (unsigned integer 32) total = add(3, 4) as u32; // Try assert_eq. assert_eq!(total, 7); } }
💡 There's lot more Primitives we didn't cover here, feel free to take a look!
We (rarely) use unsafe mutate because static mut
can be mutate and access by any function at anytime globally so that make sense to make as unsafe
.
No worry! we won't do unsafe
things anytime soon.
String, &str
We will need both &str
and String
for entire our Rust
journey.
You will know the other fancy type of string in Rust later.
Without reference
str
= string slice, immutable view into a sequence of UTF-8 bytes, reference to a portion of a string or an array of bytes and has no ownership.String
= owned, heap-allocated string, mutable, growable, and dynamic string type that you can modify at runtime.
With reference &
&str
= reference to astr
.&String
= reference to aString
.
fn main() { // Start with str let foo_str = "foo"; // &str 👈 Reference to a string slice. // Try move str let bar_str = foo_str; println!("1. bar_str: {bar_str}"); println!("2. foo_str: {foo_str}"); // Now let's try String let foo_string = foo_str.to_string(); // String 👈 So we can move it. // Try move String. let bar_string = foo_string; println!("3. bar_string: {bar_string}"); // But foo_string is already moved. 💀 // 😱 You can try uncomment 👇 this to see an error. // println!("foo_string:{foo_string}"); // ^^^^^^^^^^^^ value borrowed here after move // So we need & to make a reference. // 1️⃣ let other borrow `&` instead of move. let borrowed_bar_string = &bar_string; println!("4. bar_string: {bar_string}"); // Still can access. println!("5. borrowed_bar_string: {borrowed_bar_string}"); // Also here. // 2️⃣ or make a clone/copy instead of move. let borrowed_bar_string = bar_string.clone(); println!("6. bar_string: {bar_string}"); // Still can access. println!("7. borrowed_bar_string: {borrowed_bar_string}"); // Also here. }
So we need &
to borrow the instead of moving it.
Anyway we tend to avoid clone
/copy
to reduce overhead aka zero-copy as possible.
Oh, We also can convert between &str
and String
.
fn main() { let foo_str = "str and String"; let bar_string = String::from("str and String"); // String → &str let bar_str = bar_string.as_str(); println!("bar_string: {bar_string}"); println!("bar_str: {bar_str}"); // &str → String assert_eq!(bar_string, foo_str.to_string()); }
One more thing!
'static str
=str
with a'static
lifetime. It's a reference to a string that is valid for the entire duration of the program (which is both good and bad).
It's OK
to confuse about str
, String
and &str
, but no worries! we will get use to it in the end.