Rust
Getting started
Install via script
# install
curl https://sh.rustup.rs -aSf | sh
source $HOME/.cargo/env
export PATH="$HOME/.cargo/bin:$PATH"
# check installation
cargo -V
rustc -V
Create a template project via command cargo new --bin hello-world
or cargo init
(existing repo)
hello-world/
├── Cargo.toml # manifest
└── src
└── main.rs # source
# compile and run a binary or example
cargo run
# compile current package, binary => ./target/debug/hello-world
cargo build
# run tests
cargo test
# publish to registry crates.io
cargo publish
Variables
// let keyword creates a new variable
let x = 8;
// Use operator : to set type explicitly
let y: &str = "16";
// all variables immutable by default, unless mut keyword is used
let mut z = 32;
mut += 8;
// shadowing allows reusing variables names
let y: i16 = 16;
// only value-assigned variables can be printed
println!("{}", x);
Data types
Integer Types in Rust
Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize
Floating-point Types, boolean and character types
let x = 2.0; // f64
let y: f32 = 3.0; // f32
Boolean: one byte size
let t:bool = true;
if t {println!("happy")} else {println!("sad")}
Character: single quote, useful functions is_numeric
and is_alphabetic
let z = 'ℤ';
let heart_eyed_cat = '😻';
println!("{:?}, {:?}", z.is_numeric(), heart_eyed_cat.is_alphabetic());
Tuple: multiple types grouped, fixed sized.
let tup: (i32, f64, u8) = (500, 6.4, 1);
// Use pattern matching to destructure a tuple
let (x, y, z) = tup;
// tuple element accessed with an index
assert!(tup.0 == 500);
assert!(x == 500);
Array: same type, fixed size (at compile time).
// explicit elements, inferred type
let a = [1, 2, 3, 4, 5];
// explicit type
let b: [i32; 5] = [1, 2, 3, 4, 5];
assert_eq!(3, b[3]);
// all zeros, size 5, inferred type
let c = [0; 5];
assert_eq!(c, [0, 0, 0, 0, 0]);
assert_eq!(c.len(), 5);
//arrays can be sliced
assert_eq!([2, 3, 4], &a[1..4]);
more on arrays & slices here
Strings: Usually refers to String
and string slice str&
types.
let black = "#000"; //&str
let white = String::from("#FFF"); //String
let blanc = white.as_ref(); //&str
assert!(black == blanc); //expression fails but is valid
String vs. &str
let mut r: String;
r = "red".to_string();
r = String::from("red");
r = "red".to_owned(); //clones the reference
r = format!("red {}", "sea"));
r = "Red color!".to_string().replace("Red", "Blue");
r = "rEd".to_lowercase();
let mut b: &str;
b = "blue";
b = &String::from("abc")[0..1];
b = " hello there ".trim();
function into()
will cast to left side expression type when possible:
b = "blue".into();
r = "red".into();
Control flow
if/else
andelse if
// if/else
if a > b {a} else {b}
// else if
if a % 2 == 0{
println!("divisible by 2");
}else if a % 3 == 0{
println!("divisible by 3");
}else{
println!("other");
}
// if in a let statement
let number = if condition { 5 } else { 6 };
- Another variant
if let
allows to match one case and ignore the rest:
if let days::Sunday = today{
println!("Relax!");
}else{
println!("Be productive!"); //not ignoring the rest
}
// as opposed to match (see below)
match today{
days::Sunday => println!("Relax!"),
_ => println!("!Be productive"),
}
- Three types of loops in Rust:
loop
,while
,for
// loop will cycle until explicitly told to stop
loop {
x = y + z;
break;
}
// add a return value after break when using let
let total = loop {
count = count + 8;
break count;
};
// while
while number < limit {
number += 1;
}
// for loop with an iterator, e.g., array.iter()
for i in iterator{
println!(i);
}
// in reverse and using a Range
for n in (1..4).rev() {
println!(n);
}
match
Sorts using a expression through different arms of the structure. Used with primitive types, enum/option
match today {
days::Monday => "Meetings",
days::Tuesday => "Laundry",
days::Wednesday => "Movies",
days::Thursday => {
println!("almost Friday!");
"Planning"
},
_ => () // any other value
}
let mario = Some("Mario"); //Option<&str>
match name { //match against an Option<&str> instance
None => None,
Some(name) => Some("Hello " + name), // use the value
}
Search for a match and returns Option<T>
Data structures
Struct
Simple custom data type. Good as a placeholder or template for structured data. Use update syntax to create templates with defaults and change some.
//C-like structure: named fields
struct ClassicPoint {
x: f32,
y: f32,
z: f32
}
// tuple-like structure: unnamed fields (use index)
struct TuplePoint(f32, f32, f32);
// unit, placeholder like structure
#[derive(Debug)] // makes it printable
struct UnitStruct();
let origin: ClassicPoint = ClassicPoint{x: 0.3, y: 0.4, z: 0.1};
let end = TuplePoint(1.0, 4.0, 3.0);
let unit = UnitStruct();
// destructuring, or access via field name or index
let ClassicPoint{x: origin_x, y: origin_y, z: origin_z} = origin;
//update syntax, useful for updating large structs (templates)
let new_origin = Point { x: 5.2, ..origin };
println!("{} != {} != {}", origin.x, new_origin.x, end.0);
println!({:?}, unit);
Enum
Algebraic data types in Rust. Widely use via Options
, pattern matching match
, and the construct if let
– you can put any kind of data inside an enum variant.
enum IpAddr{
V4(u8, u8, u8, u8),
V6(String),
}
enum Expr{
Null,
Add(i32, i32),
Sub(i32, i32),
Mul(i32, i32),
Div {dividend: i32, divisor: i32},
Val(i32),
}
let ipv4 = IpAddr::V4(127, 0, 0, 1);
let quotient = Expr::Div{dividend: 10, divisor: 2};
let sum = Expr::Add(40, 2);
And implement in the Enums & Structs using impl
impl IpAddr{
fn print(&self){
println!("{:?}", &self);
}
}
ipv4.print();
The Option
Enum
Value encoded could be something or nothing so that compiler is able to check all cases. Replaces null references (nonexistent in Rust) therefore can't be used directly and null cases must be managed explicitly. Assume only Option<T>
may contain null values. The match
control flow is used to deal with all cases .
// part of std, no need to include
enum Option<T> {
Some(T),
None,
}
let some_int: Option<i8> = Some(5);
let some_string = Some("wfh");
let none_float: Option<i32> = None; // for None; type must be explicit
let sum = 10 + some_int; //error! can't be used directly
Functions
pub fn final_price(price: i16, discount: i16) -> i16 {
if (discount > price){
// early return
return price;
}
price - discount
}
fn main() {
assert_eq!(90, final_price(100, 10));
}
reference
values are moved by default instead of being copied, with exceptions. Use &
to pass by reference instead, applicable to function parameters too.
let p1 = Point {x: 1, y: 2};
//let p2 = p1; //invalid, values moved to p2, can't be used via p1 (below)
let p2 = &p1;
println!("{}", p1.x);
print_point(&p1); //usable as function parameter
fn print_point(point: &Point) {
println!("x: {}, y: {}", point.x, point.y)
}
clone types
#[derive(Clone, Debug)]
struct Point {
x: i32,
y: i32,
}
let p2 = p1.clone();
print_point_clone(p1.clone());
println!("{}", p2.x);
println!("{}", p1.x);
fn print_point_clone(point: Point) {
println!("x: {}, y: {}", point.x, point.y)
}
Copy types
special basic types aren't moved, so can be copied without issues, implement Copy in a structure to avoid moving values. Copy requires Clone. Copy can only be derived for a type containing values that implement Copy.
let num1 = 42; //basic types like integer aren't moved
let num2 = num1; //so can be copied
println!("{}", num1);
#[derive(Clone, Copy, Debug)]
struct Point {
x: i32,
y: i32,
}
let p2 = p1 // will work as expected!
mutable references
all is by default immutable, to change values pass by reference and make mutable
fn inc_x(point: &mut Point){
point.x +=1;
}
let mut p1 = Point {x: 1, y: 2};
inc_x(&mut p1);
methods
Add to custom types
impl Point { //impl Type construct
fn dist_from_origin(&self) -> f64{ //special parameter &self, instance called on
let sum_of_squares = self.x.pow(2) + self.y.pow(2); //calling methods on simple types!
(sum_of_squares as f64).sqrt() //cast the values using keyword as
}
fn translate(&mut self, dx: i32, dy: i32){ //here self is mutable
self.x += dx;
self.y += dy;
}
}
constructors
new: not a constructor, common idiom, static method = associated function, doesn't take self, Self is the type of the self value (or use the custom type name, e.g., Point)
impl Point{
fn new(x: i32, y:i32) -> Self {
Self { x: x, y: y }
}
}
fn start(x: i32, y:i32) -> Self {
Self { x, y } //shorthand if value and field have same name
}
fn origin() -> Self { //multiple constructors
Point {x: 0, y: 0}
}
}
let new_point = Point::new(11, 22); // allocates values on the stack
Modules
mod sausage_factory {
pub fn make_sausage() {
println!("sausage!");
}
}
fn main() {
sausage_factory::make_sausage();
}
Macro
Define a macro and invoke withmy_macro!(args)
macro_rules! my_macro{
($val:expr) => {
String::from("Super ") + $val
};
}
Tests
Use assertions to check statements (debug or release), triggers panic! macros.
let flag = is_enabled(x, y);
// panic message is string value of flag
assert(flag);
// using a custom panic message
assert(flag, "not enabled");
assert(flag, "not enabled: {}, {}", x, y);
writing a test
Use keywords #[cfg(test)]
and #[test]
to write your tests. Use assert
, assert_eq
, assert_neq
or custom macros. Run using cargo test
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_twice_of_positive_numbers() {
assert_eq!(double(4), 4 * 2);
}
#[test]
fn returns_twice_of_negative_numbers() {
assert_eq!(double(-4), -4 * 2);
}
#[test]
fn test_my_macro() {
assert_eq!(my_macro!("charge"), "Super charge");
}
}
Errors
Resources
- https://doc.rust-lang.org/stable/book/Most authorative reference with theory and runnable code samples
- https://doc.rust-lang.org/stable/rust-by-example/Exercises and code samples by topic
- https://rust-lang-nursery.github.io/rust-cookbook/Hands-on online book for common tasks and best practices
- https://users.rust-lang.org/ Official forum. Great place to find more learning resources
- https://github.com/rust-lang/rustlingsA starter collection of exercises with the basics and more!
- https://doc.rust-lang.org/nightly/nomicon/More advanced online book with unsafe code and low level details
- https://github.com/ctjhoa/rust-learningA bunch of links to blog posts, articles, videos, etc for learning Rust
- https://exercism.io/tracks/rustRust track to practice, compare solutions with others and get mentored
- https://stevedonovan.github.io/rust-gentle-intro/Introductory book filled with examples and concise explanations
- https://github.com/RalfJung/rust-101Alternative book with a different structure from Basic to Advanced
- https://towardsdatascience.com/you-want-to-learn-rust-but-you-dont-know-where-to-start-fc826402d5baComplete Free Resources for Rust Beginners
- https://github.com/brson/rust-anthology/blob/master/master-list.mdThis is a collection of substantial blog posts about Rust.
- https://github.com/rust-unofficial/awesome-rustA curated list of Rust code and resources.
- https://ferrous-systems.github.io/teaching-material/Teaching slides with concepts from basic to advanced
- https://github.com/pretzelhammer/rust-blog/blob/master/posts/learning-rust-in-2020.md#tldr
- https://github.com/pretzelhammer/rust-blog