Command to create a project:
cargo new hello
You can compile and run the project now with:
cargo run
This builds the project in a directory called target/debug.
To build the project in the target/release directory intended for prod, just run:
cargo run --release
A good tip to improve the code with idiomatic rust coding hints/warnings is to run:
cargo clippy
Here is a command to view the standard library in the default browser:
rustup doc --std
Crates are about code sharing between projects while modules are about code sharing within a project.
This can take place in a package library. This can be created with:
cargo new todo --lib
Cargo modules is a useful tool to manage modules. It can be installed with:
cargo install cargo-modules
Then, project's modules tree can be displayed with:
cargo modules generate tree
The displayed tree can be refined with options:
cargo modules generate tree --with-types
String slices refered to as str
are almost always handled in the shape of borrowed string slices &str.
See also the Rust documentation on references and borrowing.
A string literal stated
let msg = "Hello 🌎";
is a borrowed string slice.
The other string type is String. Data in a borrowed string slice cannot be modified while data in a String can be modified.
A String can be obtained by applying the to_string() method on a borrowed string slice:
let msg = "ab🎉".to_string();
or else by passing the borrowed string slice to String::from:
let msg = String::from("ab🎉");
Internally, a borrowed string slice is made up of a pointer to some byte and a length. The length is the number of unicode characters in the string.
Bytes can be extracted from a borrowed string slice with the bytes() method: word.bytes();
An iterator on unicode scalars can be built with word.chars();
Additionally, an iterator on graphemes can be retrieved using a package called unicode-segmentation with:
graphemes(my_string, true)
A given item in the graphemes can then be accessed with by appending a statement like.nth(3)
All of the helper methods to manipulate String objects are documented here.
A String can be converted into a &str with the .as_str() method.
It follows that string slices can manipulated in the shape of String objects
and the latter can be converted back into string slices with as_str().
For instance two &str can be concatenated into a string c with:
let a = "Hello";
let b = " World";
let c = format!("{}{}", a, b);
let rust = "\x52\x75\x73\x74";
println!("{}", rust);
struct Square {
width: u32,
height: u32,
}
impl Square {
fn area(&self) -> u32 {
self.width * self.height
}
fn whats_my_width(&self) -> u32 {
self.width
}
fn change_width(&mut self, new_width: u32){
self.width = new_width;
}
}
#[derive(Debug)]
struct RedFox {
enemy: bool,
life: u32,
}
trait Noisy {
fn get_noise(&self) -> &str;
}
impl Noisy for RedFox {
fn get_noise(&self) -> &str { "Meow?" }
}
fn print_noise<T: Noisy>(item: T) {
println!("", item.get_noise());
}
impl Noisy for u8 {
fn get_noise(&self) -> &str { "BYTE!" }
}
fn main() {
print_noise(5_u8); // prints "BYTE!"
}
There are two other types of Struct. One is the tuple like Struct:
struct Coordinates(i32, i32, i32);
The other is the unit like Struct which is useful when combined with Traits:
struct UnitStruct;
struct Course {
headline: String,
author: String,
}
impl Drop for Course {
fn drop(&mut self) {
println!("Dropping: {}", self.author);
}
}
fn main() {
let course1 = Course{ headline: String::from("Headline!"), author: String::from("Tyler"), };
drop(course1);
}
trait Clone: Sized {
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &Self) {
*self = source.clone()
}
}
Copy is a shallow Clone
From and Into, plus: TryFrom and TryInto
fn into(self) -> T: take self and returns a value of type T.
fn from(T) -> Self: take a value of type T and returns self.
use std::ops::Add;
#[derive(Debug)]
struct Point<T> {
x: T,
y: T
}
fn main() {
let coord = Point{ x: 5.0, y: 5.0 };
let coord2 = Point{ x: 1.0, y: 2.0 };
let sum = coord + coord2;
println!("{:?}", sum);
}
impl<T> Add for Point<T>
where
T: Add<Output = T> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Point {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
Fn is a family of closures and functions that you can call multiple times without restrictions. It borrows values from the environment immutably. It includes all fn functions.
FnMut is a family of closures and functions that you can call multiple times if the closure itself is declared mut. It immutably borrows values.
FnOnce is a family of closures that can be called once if the caller owns the closure. The closure cannot take ownership of the same variables more than once.
Therefore, every Fn meets the requirements for FnMut and every FnMut meets the requirements for FnOnce. It means that Fn is the most exclusive and the most powerful in this set of three Traits.
Examples:
|| drop(v) FnOnce ---> FnOnce
|args| v.contains(arg) ---> Fn
|args| v.push(arg) ---> FnMut
Iterator
Every reference has a Lifetime. Most of the time, Lifetimes are implicit and inferred.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
Syntax for lifetime in a struct is as follows:
struct MyString<'a> {
text: &'a str
}
Here is an example of a variable defined with a static lifetime:
let s: &'static str = "I have static lifetime";
let mut v: Vec<i32> = Vec::new();
v.push(2);
v.push(4);
v.push(6);
let x = v.pop(); // x is 6
println("{}", v[1]);// prints "4"
let mut u = vec![2, 4, 6];
Other ways to instanciate vectors:
let vect= Vec::<i32>::with_capacity(2);
println!("{}", vect.capacity());
let v: Vec<i32> = (0..5).collect();
println!("{:?}", v);
Vector API examples:
let mut nums: Vec<i32> = vec![];
nums.push(1);
nums.push(2);
nums.push(3);
let pop = nums.pop(); // returns Option<T>: None or Some(T)
println!("{:?}", pop);
let number = pop.unwrap();
println!("{}", number);
let two = nums[1]; // copy
// &nums[1], creates a reference if copy is not available
// (here we get a copy since i32 is a primitive type)
println!("{}", two);
let one = nums.first(); // return an Option<T>
// so None if nums is empty, else Some<T>
println!("{:?}", one);
// .last
// .first_mut and .last_mut will borrow mutable references
println!("{}", nums.len()); // return a value of length
println!("{}", nums.is_empty()); // bool
nums.insert(0, 10);
nums.insert(3, 12);
nums.insert(2, 25);
nums.remove(3);
nums.sort();
println!("{:?}", nums);
nums.reverse();
println!("{:?}", nums);
nums.shuffle(&mut thread_rng());
println!("{:?}", nums);
Vec is an example of a standard object that implements the Iterator Trait.
Example 1:
let vec2 = vec![1, 2, 3];
let mut iter = (&vec2).into_iter();
while let Some(v) = iter.next() {
println!("{}", v);
}
Example 2:
#[derive(Debug)]
struct Item {
name: String,
}
fn check_inventory(items: Vec<Item>, product: String) -> Vec<Item> {
items.into_iter().filter(|i| i.name == product).collect()
}
fn main() {
let mut vec: Vec<Item> = Vec::new();
vec.push(Item { name: String::from("coat") });
vec.push(Item { name: String::from("shirt") });
vec.push(Item { name: String::from("shorts") });
vec.push(Item { name: String::from("shoes") });
let checked = check_inventory(vec, String::from("shirt"));
println!("{:?}", checked);
}
Example 3:
#[derive(Debug)]
struct Range {
start: u32,
end: u32,
}
impl Iterator for Range {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.start >= self.end {
return None;
}
let result = Some(self.start);
self.start += 1;
result
}
}
fn main() {
let mut range = Range {start: 0, end: 10};
// for r in range {
// println!("{}", r);
// }
let vec: Vec<u32> = range.filter(|x| x % 2 == 0).collect();
println!("{:?}",vec);
}
let v: Vec<i32> = (0..5).collect();
println!("{:?}", v);
let sv: &[i32] = &v[2..4];
println!("{:?}", sv);
A slice is a fat pointer i.e. a non owning reference to a range of consecutive values.
let mut h: HashMap<u8, bool> = HashMap::new();
h.insert(5, true);
h.insert(6, false);
let have_five = h.remove(&5).unwrap();
Hashmap API examples:
let mut hm = HashMap::new();
hm.insert(1, 1);
hm.insert(5, 2);
hm.insert(30, 3);
let old = hm.insert(30, 4);
println!("{:?}", hm);
println!("{:?}", old);
println!("{:?}", hm.contains_key(&8));
println!("{:?}", hm.get(&5));
let one = hm.remove(&1);
println!("{:?}", one);
let removed = hm.remove_entry(&5);
println!("{:?}", removed);
hm.clear();
println!("{}", hm.is_empty());
Other collections: VecDeque, LinkedList, HashSet, BinaryHeap, BTreeMap, BTreeSet
let mut hs = HashSet::new();
hs.insert(1);
hs.insert(2);
hs.insert(3);
hs.insert(4);
hs.remove(&2);
for x in hs.iter() {
println!("inter: {}", x);
}
let mut hs2 = HashSet::new();
hs2.insert(1);
hs2.insert(3);
hs2.insert(5);
hs2.insert(7);
for x in hs.intersection(&hs2) {
println!("intersection: {}", x);
}
let intersection = &hs & &hs2;
for x in intersection {
println!("short hand way: {}", x);
}
let union = &hs | &hs2;
for x in union {
println!("union: {}", x);
}
enum Color {
Red,
Green,
Blue,
}
let color = Color::Red;
enum DispenserItem {
Empty,
Ammo(u8),
Things(String, i32),
Place {x: i32, y: i32},
}
use DispenserItem::*;
let item1 = Ammo(69);
let item2 = Things("hat".to_string(), 7);
enum Pet {dog, cat, fish}
impl Pet {
fn what_am_i(self) -> &'static str {
match self {
Pet::dog => "I am a dog",
Pet::cat => "I am a cat",
Pet::fish => "I am a fish",
}
}
}
enum Option<T> {
Some(T),
None,
}
let mut x: Option<i32> = None;
x = Some(5);
x.is_some(); // true
x.is_none(); false
for i in x {
println!("{}", i); // prints 5
}
The match expression handles the case when we can have Some
enum Pet {dog, cat, fish}
fn main () {
let dog = Pet::dog;
println!("{}", dog.what_am_i());
let some_number = Some(5);
let some_string = Some("a string");
let nothing: Option<i32> = None;
let x: i32 = 5;
let y: Option<i32> = Some(5);
let sum = x + y;
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}", six);
let noneUnw = None.unwrap_or(7);
println!("unw: {:?}", noneUnw);
what_pet("dog");
what_pet("cat");
what_pet("cow");
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn plus_one_unw(x: Option<i32>) -> i32 {
match x {
None => 0,
Some(i) => i + 1,
}
}
fn what_pet(input: &str) {
match input {
"dog" => println!("I have a dog!"),
"fish" => println!("I have a fish!"),
"cat" => println!("I have a cat!"),
_ => println!("I have no clue what pet I have"),
}
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Example with Result:
use std::fs::File;
fn main() {
let res = File::open("foo");
let f = res.unwrap();
}
fn main() {
let res = File::open("foo");
let f = res.expect("error message");
}
fn main() {
let res = File::open("foo");
let f = res.is_ok() {
let f = res.unwrap();
}
}
fn main() {
let res = File::open("foo");
match res {
Ok(f) => { /* do stuff */ },
Err(e) => { /* do stuff */ },
}
}
There are 3 rules to ownership:
x: &mut i32
*x // a mutable i32
x: &i32
*x: // an immutable i32
At any time, it is possible to have one mutable reference or any number of immutable references to a given value.
A borrowed variable passed to a function can be dereferenced in two ways. The first way is automated deferencing:
fn do_stuff(s: &mut String) {
s.insert_str(0, "Hi, ");
}
And the second way is manual:
fn do_stuff(s: &mut String) {
*s = String::from("Replacement")
}
Rust also has the raw pointer types *mut T and *const T. Raw pointers really are just like pointers in C++. Using a raw pointer is unsafe, because Rust makes no effort to track what it points to. For example, raw pointers may be null, or they may point to memory that has been freed or that now contains a value of a different type.
All the classic pointer mistakes of C++ are offered for your enjoyment. However, you may only dereference raw pointers within an unsafe block. An unsafe block is Rust’s opt-in mechanism for advanced language features whose safety is up to you.
Box is a smart pointer that allows to allocate data on the heap in a straighforward way:
let t = (12, "eggs"); // created on the stack
let b = Box::new(t); // created on the heap, but b was stored on the stack
println!("{:?}", b);
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
println!("{:?}", y);
Rc is a reference counter that handles and count multiple references to a value.
let s1 = Rc::new(String::from("Pointer"));
let s2 = s1.clone();
let s3 = s2.clone();
println!("{}, {},{}", s1.contains("Point"), s2, s3.contains("er"));
RefCell allows to mutate data hold in an object whose reference is immutable.
use std::rc::Rc;
use std::cell::RefCell;
struct Flagger {
is_true: RefCell<bool>,
}
let flag= Flagger { is_true: Rc::new(RefCell::new(true)) };
// borrow returns Ref<T>
// borrow_mut return RefMut<T>
let reference = Rc::new(flag.is_true.clone());
println!("{:?}", reference);
let mut mut_ref = reference.borrow_mut();
*mut_ref = false; // dereference first to access inside
println!("{}", mut_ref);
Errors split into two categories:
Example on how to catch an error at opening a file:
let file = File::open("error.txt");
let file = match file {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("error.txt") {
Ok(file_created) => file_created,
Err(err) => panic!("Cannot create the file: {:?}", err),
},
_ => panic!("It was some other error kind"),
},
};
Here is a simple way to panic and get information on error with logs:
let file = File::open("error.txt").expect("Error opening the file!");
Finally, an error that occurs in a function can be propagated upwards to the calling context by adding a question mark to the calling statement like here:
fn open_file() -> Result<File, Error> {
let file = File::open("error.txt")?;
Ok(file)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = 2 + 2;
assert_ne!(result, 5);
}
#[test]
#[should_panic]
fn it_fails(){
panic!("Test failed!");
}
#[test]
fn call_simple_add(){
assert!(simple_add());
}
}
fn simple_add() -> bool {
if 2+2 == 4 {
true
} else {
false
}
}
use std::thread;
fn main() {
let handle = thread::spawn(move || {
println!("Hello from a thread!")
});
handle.join().unwrap();
println!("Hello from main");
}
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let mut thread_handles = Vec::new();
for e in v {
// Here the move keyword is forcing the closure to take ownership:
thread_handles.push(thread::spawn(move || println!("{:?}", e)));
}
println!("Main thread!");
for handle in thread_handles {
handle.join().unwrap();
}
}
Threads can communicate between each other with channels. A channel has a transmitter and a receiver. A channel is considered closed when either the transmitter or the receiver is dropped.
use std::thread;
use std::sync::mpsc; // multi producer single consumer
fn main() {
let (transmitter, receiver) = mpsc::channel();
let val = String::from("Transmitting!");
thread::spawn(move || {
transmitter.send(val).unwrap();
});
let msg = receiver.recv().unwrap();
println!("{}", msg);
}
Types that implement Send are safe to pass by value to another thread. They can be moved accross threads.
Types that implement Sync are safe to pass by non mutable reference to another thread. They can be shared accross threads.
use std::thread;
use std::sync::Arc;
fn main() {
let rc1 = Arc::new(String::from("test"));
let rc2 = rc1.clone();
thread::spawn(move || {
rc2;
});
}
Mutexes allow to manage the access of a variable by several thread.
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..8 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("{}", counter.lock().unwrap());
}
rustup update
rustc --version
cargo install cargo-generate
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
nvm install 16.15.0
Run the command:
cargo generate --git https://github.com/rustwasm/wasm-pack-template
and enter a project name, e.g. wasm-game-of-life when prompted.
cd wasm-game-of-life
wasm-pack build
npm init wasm-app www
cd www
npm start