- "The book": https://doc.rust-lang.org/stable/book/
- Playground: https://play.rust-lang.org/
Install Rust in WSL (from https://www.rust-lang.org/tools/install):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo whatever
# create a package in `whatever` directory under the current path.
`whatever/src/main.rs` will have `hello world`.
cd whatever
cargo check # check if it compiles without building
cargo building # build the package
cargo run # run
Every variable is immutable, have to make it mutable with mut
:
let mut something = 1;
References passed to functions can also be mutable or not regardless of the
underlying variable. E.g., we can create an immutable reference to a mutable
variable. In the code below read_line
needs a mut string
, so we pass
&mut guess
instead of just &guess
.
read_line
appends whatever is read from the command line to the argument. It
does not overwrite anything. Here, it doesn't matter because guess
is empty.
use std::io;
fn main() {
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
expect
: The result of read_line
is an enum of type io::Result
. It can be
Ok
or Err
. If the value is Err
then expect
is called that crashes the
program and displays the message passed to it (Failed to
). If the value is
Ok
then expect
will return the number of bytes read which we are not using
here. We can use it like this:
let bytes = io::stdin()
// Read from input and append to guess.
.read_line(&mut guess)
.expect("Failed to read line");
println!("Read {} bytes", bytes);
Finally, we have the placeholder similar to printf(%v)
in Go.
Edit Cargo.toml
and add dependencies like rand = "0.8.3"
. Then cargo build
.
cargo update
ignores Cargo.lock
and grabs the latest versions of libraries
(that fit the versions specified in Cargo.toml
).
Now, we can use rand
here like this:
use rand::Rng;
fn main() {
println!("Guess the number!");
// Generate a "random" number between 1 and 100.
// Alternative param to "1..101" is "1..=100".
let secret_number = rand::thread_rng().gen_range(1..101);
// removed
}
1..101
range == [1, 101)
== number between 1 and 100 == 1..=100
.
The VS Code Rust language server shows us the docs for methods (and does other
things like autocomplete), but it's also possible to see the crate docs with
crate doc --open
. This will build the docs for each of the dependencies and
open them in the browser.
To do a comparison, we add std::cmp::Ordering
which is another enum with three
values: Less/Greater/Equal
.
// ch02/guessing_game/src/main.rs
use std::cmp::Ordering;
fn main() {
// removed
// Create a mutable string.
let mut guess = String::new();
let bytes = io::stdin()
// Read from input and append to guess.
.read_line(&mut guess)
.expect("Failed to read line");
// cmp compares two values
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
// removed
}
cmp
compares two values. Usable on anything that can be compared. The result
is an enum of Ordering
. We can use the match
to do something based on what
was returned. In this case we have three arms
(choices). Each arm has a
pattern
(e.g., Ordering::Less
) and an action println!
.
This won't compile because secret_number
is of type integer
and guess
is a
string
so we cannot compare them.
let guess: u32 = guess.trim().parse().expect("Please type a number!");
Instead of creating a new variable to store guess
converted to an integer, we
can shadow
the previous value. This allows us to reuse the variable name and
usually used in situations like this.
guess.trim()
removes whitespace before/after the string. The string has the
new line character(s) because we pressed enter to finish it so it must be
trimmed.
.parse()
converts a string into a number. To tell Rust which kind of number,
we annotate the variable with let guess: u32
.
.expect
returns the error if parse
returns an error and the converted number
if the return value is Ok
.
We can do an infinite loop with
loop {
println!("Please input your guess.");
// removed
println!("You guessed: {}", guess);
}
We can break out of it with break
. We want to leave the loop when we guess the
number correctly so we add to the actions for the Ordering::Equal
arm.
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
Entering a non-number will crash the program. Let's handle it. Instead of
calling expect
we match
on the return value of parse
.
let guess: u32 = match guess.trim().parse() {
Ok(num) => num, // If parse worked correctly, store the number in guess.
Err(_) => continue, // If parse returned an error, go back to the beginning of the loop.
};
If parse
worked correctly it returns an Ok
value that contains the number.
Otherwise, it returns an Err
with the error message. In Err(_)
we are
catching every message (with _
).
Some programming concepts in Rust.
We already know this. Every variable is immutable unless we use mut
. If we
want to modify an immutable variable, the compiler warns us and does not compile
the program. See ch03/variables/src/main.rs.
Created with const
keyword. We cannot use mut
with them because they are
always immutable. Can only be set to a constant expression not something that is
calculated at runtime.
const FIRST_NAME = "Parsia";
const TIME: u32 = 10 * 20 * 30;
Naming convention: All uppercase with underscore between words.
Declare a variable with the same name. Interestingly, this can be done in specific scopes. E.g., we can shadow a variable inside a block but it will not be modified outside.
// ch03/shadowing/src/main.rs
fn main() {
let x = 5;
let x = x + 1; // x = 6
// Seems like we can just create random blocks here.
{
let x = x * 2; // x = 12
// x is only shadowed in this scope.
println!("The value of x in the inner scope is: {}", x);
}
// x = 6 because this one is not touched.
println!("The value of x is: {}", x);
}
Seems like we can just make blocks with { }
.
Differences with mut
:
- We can shadow an immutable variable and create an immutable variable with the same name.
- We can change the type and reuse the name. We cannot change the type of a mutable variable.
This works because we are shadowing spaces
and creating a new variable of type
int.
fn main() {
let spaces = " ";
let spaces = spaces.len();
}
We can also make the new spaces
mutable:
// ch03/shadowing2/src/main.rs
fn main() {
let spaces = " ";
println!("spaces: {}", spaces);
let mut spaces = spaces.len();
println!("spaces: {}", spaces);
spaces = 1234;
println!("spaces: {}", spaces);
}
returns:
spaces: [prints three spaces]
spaces: 3
spaces: 1234
If we make a variable mutable but do not modify it, the compiler will give us a warning saying it should not be mutable.
fn main() {
let spaces = " ";
println!("spaces: {}", spaces);
let mut spaces = spaces.len();
println!("spaces: {}", spaces);
}
The shadowing spaces
(2nd one) is mutable but not modified so we get:
Compiling playground v0.0.1 (/playground)
warning: variable does not need to be mutable
--> src/main.rs:4:9
|
4 | let mut spaces = spaces.len();
| ----^^^^^^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
warning: `playground` (bin "playground") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 1.79s
Running `target/debug/playground`
Standard Output
spaces:
spaces: 3
Like we have seen before. We can have 8, 16, 32, 64, and 128-bit integers.
Default is i32
.
- Signed:
i8 i16 i32 i64 i128
- Unsigned:
u8 u16 u32 u64 u128
There's also isize
(signed) and usize
(unsigned) which are based on the
machine. E.g., i64 for 64-bit machines.
When writing integer literals we can use some help:
_
is ignored so1_200
and1200
are equal.- Start
- Hex number with
0x
:0xAB
. - Octal number with
0o
(zero and the lettero
):0o77
. - Binary number with
0b
:0b0011_1111
. See the_
for better readability. - Byte with
b
(onlyu8
):b'A'
.
- Hex number with
f32
and f64
(default). To specify f32
we need to annotate it:
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
As you can imagine.
// ch03/numeric_operations.rs
fn main() {
// addition
let sum = 5 + 10;
println!("5 + 10 = {}", sum); // 5 + 10 = 15
// subtraction
let difference = 95.5 - 4.3;
println!("95.5 - 4.3 = {}", difference); // 95.5 - 4.3 = 91.2
// multiplication
let product = 4 * 30;
println!("4 * 30 = {}", product); // 4 * 30 = 120
// division
let quotient = 56.7 / 32.2;
let floored = 2 / 3; // Results in 0
println!("56.7 / 32.2 = {}", quotient); // 56.7 / 32.2 = 1.7608695652173911
println!("2 / 3 = {}", floored); // 2 / 3 = 0
// remainder
let remainder = 43 % 5;
println!("43 % 5 = {}", remainder); // 43 % 5 = 3
}
true
and false
.
char
: 4-bytes Unicode Scalar values. E.g., U+0031
or emojis.
Used with '
(strings use "
).
Fixed-size array/slice of values with different types.
// ch03/tuple.rs
fn main() {
// create a tuple like this, note the type annotation.
let tup: (i32, f64, u8) = (500, 6.4, 1);
// destructure it to get the values.
let (x, y, z) = tup;
println!("x = {}, y = {}, z = {}", x, y, z);
// x = 500, y = 6.4, z = 1
// can also get the value by `var.index`.
println!("tup.0 = {}, tup.1 = {}, tup.2 = {}", tup.0, tup.1, tup.2);
// tup.0 = 500, tup.1 = 6.4, tup.2 = 1
}
Fixed-size array of values with the same type. Goes on stack instead of heap.
// ch03/arrays.rs
fn main() {
let arr = [1, 2, 3];
println!("arr = {:?}", arr);
let strings = ["one", "two", "three"];
println!("strings = {:?}", strings);
// we can also specify the type and size. See below why we are using &str here.
let arr2: [&str; 3] = ["one", "two", "three"];
println!("arr2 = {:?}", arr2);
}
Specify one initial value for all elements:
// these are the same
let a = [3; 5];
let b = [3, 3, 3, 3, 3];
Access array elements like most other languages:
let arr = [1, 2, 3];
let b = arr[0]; // b = 1;
It's possible to access elements beyond the capacity. If the value is known when compiling, the Rust compiler will give us an error:
fn main() {
let arr2: [&str; 3] = ["one", "two", "three"];
println!("arr2 = {:?}", arr2);
println!("arr2[3] = {}", arr2[3]);
// ^^^^^^^ index out of bounds: the length is 3 but the index is 3
let c = 1 + 2;
println!("arr2[c] = {}", arr2[c]);
// ^^^^^^^ index out of bounds: the length is 3 but the index is 3
}
However, we can provide a dynamic variable (e.g., get it from the user) and the program will panic with an out of bounds access.
So I had this problem above, when you create something like this
let a = "whatever";
you are creating a string literal
or &str
which is a
read-only string and not the same as the type String
.
So when I wanted to annotate the type for the same array like this, I got an error:
let arr2: [str, 3] = ["one", "two", "three"];
^^^^^ expected `str`, found `&str`
So we have to annotate it like this with &str
but I had another problem
printing it:
let arr2: [&str, 3] = ["one", "two", "three"];
println!("arr2 = {}", arr2);
^^^^ `[&str; 3]` cannot be formatted with the default formatter
= help: the trait `std::fmt::Display` is not implemented for `[&str; 3]`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
We can use the note to pretty print:
println!("arr2 = {:?}", arr2);
// arr2 = ["one", "two", "three"]
println!("arr2 = {:#?}", arr2);
// arr2 = [
// "one",
// "two",
// "three",
// ]
Similar to other languages.
fn main() {
my_func(10, 'a');
}
// we can define parameters.
fn my_func(param1: i32, param2: char) {
println!("param1 = {}, param2 = {}", param1, param2);
// param1 = 10, param2 = a
}
The last expression in the function can act as the return value.
blocks of code evaluate to the last expression in them, and numbers by themselves are also expressions
fn main() {
println!("double_me(1) = {}", double_me(1)); // double_me(1) = 2
}
// return values
fn double_me(x: i32) -> i32 {
x + 1
}
However, if we change it to x + 1;
it becomes an statement and we get an
error.
|
6 | fn double_me(x: i32) -> i32 {
| --------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
7 | x + 1;
| - help: consider removing this semicolon
Instead, we can use the return
keyword.
fn main() {
println!("double_me(1) = {}", double_me(1));
}
// return values
fn double_me(x: i32) -> i32 {
return x + 1;
}
Similar to other languages.
fn main() {
let number = 3;
if number < 5 {
println!("less than five");
} else if number == 5 {
println!("equals five");
} else if number > 5 {
println!("more than five");
}
}
Doing unneeded parentheses around the condition returns a warning
(e.g., if (number < 5)
):
|
4 | if (number < 5) {
| ^ ^
|
= note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
|
4 - if (number < 5) {
4 + if number < 5 {
|
We can use if
in a let
statement:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {}", number);
}
We've already seen it. loop
can use break
and continue
to leave to go back
to the start. When loops are nested, these only apply only to their parent loop.
We can also have labeled loops
so we can interact with outer loops
// ch03/labeled_loop.rs
fn main() {
let mut count = 0;
'parent_loop: loop {
println!("count = {} in 'parent_loop", count);
loop {
println!("count = {} in inside loop", count);
if count == 2 {
// break out of the 'parent_loop
break 'parent_loop;
}
count += 1;
}
}
println!("End count = {}", count);
}
// count = 0 in 'parent_loop
// count = 0 in inside loop
// count = 1 in inside loop
// count = 2 in inside loop
// End count = 2
We can also return values from loop (lol wut?). We can assign the loop to a
variable and then return break value;
.
// ch03/return_value_loop.rs
fn main() {
let mut count = 0;
let counted = loop {
count += 1;
if count == 5 {
break count;
}
};
println!("counted = {}", counted); // counted = 5
}
loop
s don't have conditions. We can use while
instead.
// ch03/while.rs
fn main() {
let mut count = 0;
while count != 5 {
count += 1;
}
println!("count = {}", count); // count = 5
}
We can iterate through a collection (e.g., array but not tuple) with for
.
// ch03/while.rs
fn main() {
let strings = ["one", "two", "three"];
for s in strings {
println!("{}", s);
}
}
Trying to use for
with a tuple returns this error.
let tup: (i32, f64, u8) = (500, 6.4, 1);
for t in tup {
println!("{}", t);
}
|
10 | for t in tup {
| ^^^ `(i32, f64, u8)` is not an iterator
|
= help: the trait `Iterator` is not implemented for `(i32, f64, u8)`
= note: required because of the requirements on the impl of `IntoIterator` for `(i32, f64, u8)`
note: required by `into_iter`
The book says for
is the most common way to use loops. We can use it to repeat
things a certain number of times by using it over a range.
// rev is reversing the range.
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
// 3!
// 2!
// 1!
// LIFTOFF!!!
Stack: LIFO. Data on stack must have a known and fixed size. Faster.
Heap: Data with unknown size at compile time or a size that might change. You ask for a certain amount of space on heap, the memory allocator locates some free space and returns a pointer to it (and sets it in-use). Slower.
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
String literals are immutable (type &str
). See the section String Literals
above for more info.
We can use String
which exists on the heap. We can create them from a string
literal and modify it.
fn main() {
let mut s = String::from("hello");
// append something to it with push_str
s.push_str(", world!");
// we cannot do this because it's not a string literal.
// println!(s); // Compiler error
// print it
println!("{}", s); // hello, world!
}
When the value leaves the scope, Rust automatically calls drop
at the closing
curly braces. A String has three parts:
- ptr: Pointer to the memory with the values.
- len: Number of bytes used by the String.
- cap: Total number of bytes of memory assigned to the String by the allocator.
Let's create a String and then assign it to another variable like this:
let s1 = String::from("hello");
let s2 = s1;
The ptr
in both s1 and s2 will point to the same location on the heap with the
value of the string. The value is not copied for s2.
When both s1 and s2 go out of scope we might have gotten a double-free bug
because both wanted to free the memory. To prevent this issue, Rust makes s1
invalid as soon as the assignment happens (let s2 = s1;
). We cannot use s1
after that.
fn main() {
// create a String.
let s1 = String::from("hello");
// assign it to s2
let s2 = s1;
// try to use s1.
println!("{}", s1);
// use s2 so we don't get an "unused variable warning"
println!("{}", s2);
}
And we get an error because s1 was moved.
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:7:20
|
3 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
4 | // assign it to s2
5 | let s2 = s1;
| -- value moved here
6 | // try to use s1.
7 | println!("{}", s1);
| ^^ value borrowed here after move
But what if we want to make a copy? We use clone
.
fn main() {
// create a String.
let s1 = String::from("hello");
// clone it
let s2 = s1.clone();
// try to use s1
println!("{}", s1); // hello
// use s2 so we don't get an "unused variable warning"
println!("{}", s2); // hello
}
But we have seen assignments in other types and both work.
fn main() {
let x = 5;
let y = x;
// we can use both x and y.
println!("x = {}, y = {}", x, y); // x = 5, y = 5
}
These types have a known size at compile time and are stored on the stack. So assignment here creates a new copy of the entire object on the stack.
If a type has the Copy
trait, this behavior occurs: Integers, floats, chars,
bools, and Tuples if the types in it have all implemented the Copy
trait.
Passing a value to a function will move or copy a value like an assignment.
// ch04/func_string.rs
fn main() {
// create a string
let s = String::from("hello");
// pass it to a function
use_string(s);
// we cannot use s here anymore because it was moved to `some_string` and
// it went out of scope when `use_string` returned.
println!("{}", s);
}
fn use_string(some_string: String) {
println!("{}", some_string);
// some_string goes out of scope. drop is called. Memory is freed.
}
We get an error because s
was moved when passed to the function.
error[E0382]: borrow of moved value: `s`
--> src/main.rs:10:20
|
3 | let s = String::from("hello");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
...
6 | use_string(s);
| - value moved here
...
10 | println!("{}", s);
| ^ value borrowed here after move
But we won't have this issue if the type implements the Copy
trait (e.g., int).
// ch04/func_int.rs
fn main() {
// create an int
let x = 5;
// x does not move because i32 implements `Copy`.
use_int(x); // 5
// we can still use x here.
println!("{}", x); // 5
}
fn use_int(some_integer: i32) {
println!("{}", some_integer);
} // some_integer goes out of scope. Nothing special happens.
Same thing happens with returns. If we pass a String to a function, we need to return it from the function to be able to use it later.
To avoid this whole mess of moving when passing variables to function we can use references. A reference refers to the variable but does not own it. So when we pass a reference to the function there will be no moves.
We call the action of creating a reference borrowing.
fn main() {
// create a string
let s1 = String::from("hello");
// pass it as a reference to calculate_length
let len = calculate_length(&s1);
// we can still use s1 here.
println!("The length of '{}' is {}.", s1, len);
// The length of 'hello' is 5.
}
// note the annotation here, the param type is &String
fn calculate_length(s: &String) -> usize {
return s.len();
// `s.len()` would do the same
}
When s
goes out of scope at the end of the function, the value for s1
is not
dropped because s
does not own it.
We cannot modify s
inside.
fn main() {
// create a string
let s1 = String::from("hello");
// pass it as a reference
modify_s(&s1);
}
fn modify_s(s: &String) {
s.push_str(" yolo!");
}
We get an error.
error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
--> src/main.rs:9:5
|
8 | fn modify_s(s: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
9 | s.push_str(" yolo!");
| ^^^^^^^^^^^^^^^^^^^^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable
Let's use the suggestion and pass a mutable reference to the function.
fn main() {
// create a string
let s1 = String::from("hello");
// pass it as a reference
modify_mut_s(&mut s1);
}
fn modify_mut_s(s: &mut String) {
s.push_str(" yolo!");
}
We get another error because s1
is not mutable we cannot borrow it as mutable.
error[E0596]: cannot borrow `s1` as mutable, as it is not declared as mutable
--> src/main.rs:5:18
|
3 | let s1 = String::from("hello");
| -- help: consider changing this to be mutable: `mut s1`
4 | // pass it as a reference
5 | modify_mut_s(&mut s1);
| ^^^^^^^ cannot borrow as mutable
Let's make s1
mutable, too.
fn main() {
// create a mutable string
let mut s1 = String::from("hello");
// pass it as a mutable reference
modify_mut_s(&mut s1);
println!("{}", s1); // hello yolo!
}
fn modify_mut_s(s: &mut String) {
s.push_str(" yolo!");
}
And this works! Remember that although s1
is mutable, we could have borrowed
it as immutable.
We can only have one mutable reference to a value at a time. This code won't work:
fn main() {
// create a mutable string
let mut s1 = String::from("hello");
let r1 = &mut s1;
let r2 = &mut s1;
println!("{}, {}", r1, r2);
}
We get this error:
error[E0499]: cannot borrow `s1` as mutable more than once at a time
--> src/main.rs:6:14
|
5 | let r1 = &mut s1;
| ------- first mutable borrow occurs here
6 | let r2 = &mut s1;
| ^^^^^^^ second mutable borrow occurs here
7 |
8 | println!("{}, {}", r1, r2);
| -- first borrow later used here
This supposedly helps with data races.
We cannot also have mutable and immutable borrows at the same time.
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
}
Because we have an immutable reference in the same scope. Multiple immutable references are allowed.
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
The scope of a reference starts from where it's introduced until the last time it is used even though the block has not finished. It's a bit different from variable references. E.g., this will work because r1 and r2 are not used after r3 is created.
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
}
The compiler does not allow us to create dangling references. This won't work:
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
dangle
creates s and wants to return a reference to it. However, after dangle
returns, s goes out of scope and is deallocated. Hence, a dangling reference.
The compiler returns an error. Instead, we can return the s
.
this function's return type contains a borrowed value, but there is no value for
it to be borrowed from help: consider using the `'static` lifetime
Similar to slice in Go? You can reference a sequence in a collection without ownership.
Small function to find the index of the first word in a string.
fn first_word(s: &String) -> usize {
// converts the string to an array of bytes
let bytes = s.as_bytes();
// bytes.iter.enumerate() returns a tuple. The index and a reference to the item
// here we are destructuring the tuple
for (i, &item) in bytes.iter().enumerate() {
// compare the character with space
if item == b' ' {
return i;
}
}
// if there's no space in the string, all of it is the word
s.len()
}
However, this is not useful because it's an index to a string that might have been modified since then. Instead, we can return a String slice with the words.
Create it like this. Note the second index is exclusive. E.g., [0..5]
starts
from index 0 and ends at 4.
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} - {}", hello, world);
}
We can drop the lower range if we want to start from the beginning. So [0..5]
and [..5]
are the same.
If the higher range is the end we can drop it. E.g., [4..]
goes from index 4
to the end.
[..]
creates a slice from the whole string. let slice = &s[..]
. Now, we can
rewrite the function to return a slice.
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
String literals are slices. Remember what we saw in the string literal
section? Their type is &str
.
We can create slices of other types.
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("{:?}", slice); // [2, 3]
}
Similar to other programming languages.
// ch05/basic_struct.rs
// define a struct
struct Game {
name: String,
hours_played: u32,
path: String,
}
fn main() {
// create a mutable object
let mut game1 = Game {
name: String::from("Windows Calculator"),
hours_played: 123,
path: String::from("C:/Windows/System32/calc.exe"),
};
// access fields
println!("{}, {}, {}", game1.name, game1.hours_played, game1.path);
// Windows Calculator, 123, C:/Windows/System32/calc.exe
// change fields
game1.path = String::from("C:\\Windows\\System32\\calc.exe");
// print the modified field
println!("{}", game1.path);
// C:\Windows\System32\calc.exe
}
We cannot set specific fields to mutable. Whole object needs to be mutable or not.
Let's say we have this function to create a new Game
object.
fn build_game(name: String, hours_played: u32, path: String) -> Game {
Game {
name: name,
hours_played: hours_played,
path: path,
}
// apparently using the return keyword is _bad_.
}
If the name of the field and the variable with the value is the same like the above we can do a shorthand like this.
// complete example in ch05/field_init_shorthand.rs
fn build_game(name: String, hours_played: u32, path: String) -> Game {
Game {
name,
hours_played,
path,
}
}
We can create a new object with the values from a previous object and only modify some.
// complete example in ch05/struct_update.rs
fn main() {
// create a game object
let game1 = Game {
name: String::from("Windows Calculator"),
hours_played: 123,
path: String::from("C:/Windows/System32/calc.exe"),
};
// create game2 based on game1 with a new path
let game2 = Game {
path: String::from("C:\\Windows\\System32\\calc.exe"),
..game1
};
// access fields
println!("{}, {}, {}", game2.name, game2.hours_played, game2.path);
// Windows Calculator, 123, C:\Windows\System32\calc.exe
}
However, we moved game1 so we cannot use it anymore. Because we used the
value of String
for the name
field. If we had only copied the fixed-length
values of game1
then we would have been able to use it. Here's an example to
show that.
Note how print_game
accepts a reference, if we had passed the
actual object it would have been moved after calling the function and we could
not have used it anymore.
// ch05/struct_update_move.rs
// define a struct
struct Game {
name: String,
hours_played: u32,
path: String,
}
fn print_game(game: &Game) {
println!("{}, {}, {}", game.name, game.hours_played, game.path);
}
fn main() {
// create a game object
let game1 = Game {
name: String::from("Windows Calculator"),
hours_played: 123,
path: String::from("C:/Windows/System32/calc.exe"),
};
// create game1_new based on game1 but only reuse hours_played which is the
// only fixed-length field
let game1_new = Game {
name: String::from("Guild Wars"),
path: String::from("C:/Guild Wars/gw.exe"),
..game1
};
// we can still use game1 here because we did not "move" any of the Strings
// create game2 based on game1 with a new path
print_game(&game1);
// Windows Calculator, 123, C:/Windows/System32/calc.exe
print_game(&game1_new);
// Guild Wars, 123, C:/Guild Wars/gw.exe
// create game2_new but reuse the Strings so they are "moved"
let game1_new_new = Game {
hours_played: 6000,
..game1
};
// we cannot use game1 anymore.
print_game(&game1); // <-- error here
print_game(&game1_new_new);
}
We get an error in the last print_game(&game1)
because game1
was partially
moved when creating game1_new_new
.
error[E0382]: borrow of partially moved value: `game1`
--> src/main.rs:44:16
|
38 | let game1_new_new = Game {
| _________________________-
39 | | hours_played: 6000,
40 | | ..game1
41 | | };
| |_____- value partially moved here
...
44 | print_game(&game1); // <-- error here
| ^^^^^^ value borrowed here after partial move
|
= note: partial move occurs because `game1.path` has type `String`, which does not implement the `Copy` trait
Think of them as structs but without field names. We can access the fields by
index (see in print_game
).
// ch05/tuple_struct.rs
// define two tuple structs
struct Game(String, u32, String);
struct App(String, u32, String);
fn print_game(game: &Game) {
// we can access the tuple struct's fields with the index
println!("{}, {}, {}", game.0, game.1, game.2);
}
fn main() {
let game1 = Game(
String::from("Guild Wars"),
5000,
String::from("C:/Guild Wars/gw.exe")
);
print_game(&game1);
// Guild Wars, 5000, C:/Guild Wars/gw.exe
let app1 = App(
String::from("Windows Calculator"),
123,
String::from("C:/Windows/System32/Calc.exe")
);
// we cannot call print_game with &App because they are different structs
// although they have the same fields
print_game(&app1); // <-- error 'expected struct `Game`, found struct `App`'
}
Game
and App
are different structs although they have similar fields.
They don't have any fields.
struct Whatever;
let wt = Whatever;
We cannot use println!
to print the Game
struct.
struct Game {
name: String,
hours_played: u32,
path: String,
}
fn main() {
let game1 = Game {
name: String::from("Guild Wars"),
hours_played: 5000,
path: String::from("C:/Guild Wars/gw.exe")
};
println!("{}", game1);
}
We get an error in println!
because it does not implement the
std::fmt::Display
trait.
error[E0277]: `Game` doesn't implement `std::fmt::Display`
--> src/main.rs:15:20
|
15 | println!("{}", game1);
| ^^^^^ `Game` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Game`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
The error tells us we can use println!("{:?}", game1);
which results in
another error:
error[E0277]: `Game` doesn't implement `Debug`
--> src/main.rs:15:22
|
15 | println!("{:?}", game1);
| ^^^^^ `Game` cannot be formatted using `{:?}`
|
= help: the trait `Debug` is not implemented for `Game`
= note: add `#[derive(Debug)]` to `Game` or manually `impl Debug for Game`
= note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)
Let's add #[derive(Debug)]
to Game
.
#[derive(Debug)]
struct Game {
name: String,
hours_played: u32,
path: String,
}
fn main() {
let game1 = Game {
name: String::from("Guild Wars"),
hours_played: 5000,
path: String::from("C:/Guild Wars/gw.exe")
};
println!("{:?}", game1);
}
Finally:
Game { name: "Guild Wars", hours_played: 5000, path: "C:/Guild Wars/gw.exe" }
.
We can use the dbg!
macro to print to stderr
(println!
prints to
stdout
). It prints info about the parameter and returns their ownership. We
can also pass references to it.
#[derive(Debug)]
struct Game {
name: String,
hours_played: u32,
path: String,
}
fn main() {
let game1 = Game {
name: String::from("Guild Wars"),
hours_played: dbg!(5000),
path: String::from("C:/Guild Wars/gw.exe")
};
dbg!(&game1);
}
The result:
[src/main.rs:12] 5000 = 5000
[src/main.rs:16] &game1 = Game {
name: "Guild Wars",
hours_played: 5000,
path: "C:/Guild Wars/gw.exe",
}
We can convert print_game
into a method that we can call on Game
objects.
They are defined similar to normal functions, but their first param is always
self
.
// ch05/method1.rs
struct Game {
name: String,
hours_played: u32,
path: String,
}
impl Game {
// implement print for Game
fn print(&self) {
println!("{}, {}, {}", self.name, self.hours_played, self.path);
}
}
fn main() {
let game1 = Game {
name: String::from("Guild Wars"),
hours_played: 5000,
path: String::from("C:/Guild Wars/gw.exe"),
};
game1.print();
// Guild Wars, 5000, C:/Guild Wars/gw.exe
}
Note how we are passing &self
to print
. Methods can also take ownership of
self
and other parameters.
We can name a method the same as a field. Usually, these are getters and return the value of the field.
impl Game {
fn name(&self) -> String {
self.name
}
}
Now, we can call game1.name()
to get the value of name
.
When calling methods, Rust automatically add &
, &mut
, or *
to the object
to match the method signature. Hence, why we did not do (&game1).print()
although the receiver was &self
.
Functions inside an impl
block are called associated functions
because they
are associated with a struct.
We can define non-method associated functions (they do not have the &self
param). Let's add a function that creates a game object for calc.
// ch05/associated_method_calc.rs
struct Game {
name: String,
hours_played: u32,
path: String,
}
impl Game {
fn print(&self) {
println!("{}, {}, {}", self.name, self.hours_played, self.path);
}
fn calc() -> Game {
Game {
name: String::from("Windows Calculator"),
hours_played: 0,
path: String::from("C:/Windows/System32/calc.exe"),
}
}
}
fn main() {
let calc = Game::calc();
calc.print();
// Windows Calculator, 0, C:/Windows/System32/calc.exe
}
We can call it with Game::calc()
similar to String::from(...)
.
We can have multiple impl blocks.
We can create an enum:
enum AppType {
Utility,
Game,
}
// get an enum
let util = AppType::Utility;
We can define functions that take a param of type AppType
. Revamping the
game example from before:
// ch06/enum1.rs
// needed to print the AppType values
#[derive(Debug)]
enum AppType {
Utility,
Game,
}
struct App {
name: String,
hours_played: u32,
path: String,
app_type: AppType,
}
impl App {
fn print(&self) {
println!(
"Name: {}, Hours played: {}, Path: {}, Type: {:?}",
self.name,
self.hours_played,
self.path,
self.app_type
);
}
}
fn main() {
let calc = App {
name: String::from("Windows Calculator"),
hours_played: 123,
path: String::from("C:/Windows/System32/calc.exe"),
app_type: AppType::Utility,
};
calc.print();
// Name: Windows Calculator, Hours played: 123, Path: C:/Windows/System32/calc.exe, Type: Utility
}
Note how I have used #[derive(Debug)]
before the enum to print its value with
{:?}
. Otherwise it does not work.
We can also ditch the struct and do everything in the enum.
// ch06/enum2.rs
#[derive(Debug)]
enum AppType {
// name and path.
Utility(String, String),
// name, path, hours_played
Game(String, String, u32),
}
fn main() {
let calc = AppType::Utility(
String::from("Windows Calculator"),
String::from("C:/Windows/System32/calc.exe"),
);
let gw = AppType::Game(
String::from("Guild Wars"),
String::from("C:/Guild Wars/gw.exe"),
5000,
);
println!("{:?}", calc);
// Utility("Windows Calculator", "C:/Windows/System32/calc.exe")
println!("{:?}", gw);
// Game("Guild Wars", "C:/Guild Wars/gw.exe", 5000)
}
We can also define structs and pass them as a parameter to the enum function.
// ch06/enum3.rs
#[derive(Debug)]
struct GameStruct {
name: String,
path: String,
hours_played: u32,
}
#[derive(Debug)]
struct UtilStruct {
name: String,
path: String,
}
#[derive(Debug)]
enum AppType {
Game(GameStruct),
Utility(UtilStruct),
}
fn main() {
let calc = AppType::Utility(UtilStruct {
name: String::from("Windows Calculator"),
path: String::from("C:/Windows/System32/calc.exe"),
});
println!("{:?}", calc);
// Utility(UtilStruct { name: "Windows Calculator", path: "C:/Windows/System32/calc.exe" })
}
We can have different things in the enum like the example from the book:
enum Message {
// no data associated with it.
Quit,
// named fields like a struct
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
We can also have enum methods. Let's define the new print
method that returns
a String that represents each enum.
// ch06/enum4.rs
enum AppType {
Game(GameStruct),
Utility(UtilStruct),
}
impl AppType {
fn stringer(&self) -> String {
match self {
AppType::Game(g) => format!(
"Name: {}, Path: {}, Hours Played: {}",
g.name, g.path, g.hours_played
),
AppType::Utility(u) => format!("Name: {}, Path: {}", u.name, u.path),
}
}
}
Note how we are using match
and are able to access the enum parameters (in
this case structs). The format!
macro returns a formatted String
.
Rust does not have null but the Option
enum allows us to have None
.
enum Option<T> {
None,
Some(T),
}
We can use Some
and None
directly without importing anything.
<T> == lol yes generics
.
// Option<i32>
let some_number = Some(5);
let absent_number: Option<i32> = None; // we have to enter the type for None
// Option<&str>
let some_string = Some("a string");
We can try to write a function that extracts the value of u32
from
Option<i32>
like this:
fn extract_option(o: Option<u32>) -> u32 {
match o {
Some(i) => i,
}
}
This returns i
for Some(i)
, but the compiler complains about not handling
None
.
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:2:11
|
2 | match o {
| ^ pattern `None` not covered
|
Basically, the match should cover all possible values. They are exhaustive.
Here, we will have a dilemma. What should we return as None
? None
is the
absence of values here. A very naive way would be returning 0. But that means
Some(0)
and None
would be the same.
fn extract_option(o: Option<u32>) -> u32 {
match o {
Some(i) => i,
None => 0,
}
}
fn main() {
let s1 = Some(0);
let n1: Option<u32> = None;
println!("{}", extract_option(s1)); // 0
println!("{}", extract_option(n1)); // 0
}
I still don't know how to use None
, but let's move on.
Writing exhaustive matches will be boring if we only want to take action for a
few values. This is why we have other
. So, we can write matches like this.
match user_input {
1 => {
do1();
launch();
} // we can have a block here, too - rustfmt removes the colon here
2 => abort(),
other => try_again(other), // note how we can use `other` in the arm
}
If we don't want to use the value, we can use _
instead of other
.
match user_input {
1 => {
do1();
launch();
}
2 => abort(),
_ => try_again(), // run try_again for all other values
}
If we really don't want to do anything, we can replace try_again()
with ()
:
match user_input {
1 => {
do1();
launch();
}
2 => abort(),
_ => (), // don't do anything for all other values
}
Well, this is very confusing!
Similar to a match
but not exhaustive. These two are supposedly the same.
let config_max = Some(3);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
Because of the let
, the value of max
will be 3u8
after the if
.
let config_max = Some(3);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
This gets more confusing because if we already have a max
variable, it will be
shadowed here.
fn main() {
let config_max = Some(3);
let max = 10;
println!("{}", max); // prints "10"
if let Some(max) = config_max {
println!("{}", max); // prints "3"
}
println!("{}", max); // prints "10"
}
I should stop thinking of this as an if
. Not sure how I can handle using it
later when writing Rust. We can also have else
here which is like the _
in
match and matches everything else.
fn main() {
let config_max = Some(3);
if let Some(max) = config_max {
println!("{}", max); // prints "3"
} else {
println!("{}", "not 3");
}
}
- Crate: A binary of library.
- Crate root: The source file the compiler starts from.
src/main.rs
: Crate root of binary crate with the same name as the package.- More binary crates will be in
src/bin
.
- More binary crates will be in
src/lib.rs
: Crate root of library crate with the same name as the package.
- Package: One or more crates.
- Has
Cargo.toml
which describes how to build the crates. - Has at most one library crate and many binary crates.
- Must have at least one crate.
- Has
Modules help us organize code within a crate into groups. Also public/private
items. Defined with the mod
keyword. Modules can be nested, too.
mod applications {
mod games {
fn start() {}
}
mod utilities {
fn start_utility() {}
}
}
games
and utilities
are sibling modules. They are defined in the same module
and have the same parent. All top level modules are children of an implicit
module named crate
. In this code applications
is a child of crate
.
How to tell Rust where to find an item in the module tree. Separator is ::
.
- Absolute: Starts with the create name or
crate
. - Relative: Starts from the current module and uses
self
,super
, or an identifier in the current module.
// ch07/restaurant/src/lib.rs
mod applications {
mod games {
fn start() {}
}
mod utilities {
fn start_utility() {}
}
}
// pub makes it public, more about it later
pub fn run_game() {
// absolute path
crate::applications::games::start();
// relative path
applications::games::start();
}
applications
is defined in the same level as run_game so we can start with it
in a relative path.
We cannot build this with cargo build
because the games
module is private.
error[E0603]: module `games` is private
--> src/lib.rs:13:26
|
13 | crate::applications::games::start();
| ^^^^^ private module
|
note: the module `games` is defined here
--> src/lib.rs:2:5
|
2 | mod games {
| ^^^^^^^^^
Everything is private by default in Rust. We have to use pub
to make them
public.
Parent modules cannot see items in their child modules, but child modules can use items in their parent modules.
If we just make the games
module public (pub mod games
) we still get an
error becuse start
is still private. It must be public, too.
mod applications {
pub mod games {
pub fn start() {}
}
mod utilities {
fn start_utility() {}
}
}
// now this works
// crate::applications::games::start();
Why can we call stuff in the applications
module although it's not public?
Because run_game()
is defined in the same module.
Equal to ..
in the file system. We can refer to parent modules. delete_stuff
is defined in applications
. We can call it super
from inside games
.
mod applications {
pub mod games {
pub fn start() {}
fn delete() {
// call delete_stuff() from `applications`
super::delete_stuff();
}
}