Syntax
Comments
// Singleline Comment
/// Documentation Comment - Supports Markdown
/*
Block
Comment
*/
use
crates
Allows to bring modules and/or function into scope. In idiomatic rust always specify the full path and "import" only the minimal.
use <cratename>;
use std::io; // brings io of std into local scope
use rand::Rng; // bring Rng of rand into loacl scope
use std::time::(Duration, Instant); // brings Duration and Instant of std::time into local scope
Bring own modules into scope with the help of self
mod front_of_house {
pub mod hosting {
pub fn add_to_whitelist() {}
}
}
use self::front_of_house::hosting;
# or
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_whitelist();
}
Bring into scope two elements with the same name from different crates. In this case only bring the parent module into scope.
use std::fmt;
use std:io;
fn function1() fmt::Result {}
fn function2() io::Result<()> {}
It is also possible to rename the import with as
use std::fmt::Result;
use std:io::Result as IoResult;
fn function1() Result {}
fn function2() ioResult<()> {}
import more item at once into scope
use std::io::{self, Write};
use rand::{Rng, CryptoRng, ErrorKind::Transient};
mod
modules
split code in logical units and manage visibility (public/private) between them. Modules can contain, constants, structs, enums functions and other modules. A crate is also a modules which is created by default.
Module tree
crate
+-- front_of_house
+-- hosting
| +-- add_to_whaitlist
| +-- seat_at_table
+-- serving
+-- take_order
+-- serve_order
+-- take_payment
Path to modules can be Absolute or relative.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist!();
}
Modules visibility
- By default a child modules is private and the parent modules can't see it's content.
- Child modules can see all parent modules functionalities
- With
pub
keyword, function can be access from outside.
modules example with visibility
mod front_of_house {
pub mod hosting {
pub fn add_to_whitelist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_whitelist();
// Relative path (start with current modules)
front_of_house::hosting::add_to_whitelist();
}
use self::front_of_house::hosting
pub fn eat_at_restaurant_2() {
hosting::add_to_whitelist();
}
Variables
Rust code uses snake_case for function and variables.
const
- Constants
Constants live in their scope and cannot be changed for the entire program duration. They are written in UPPERCASE_WITH_UNDERSCORES_BETWEEN
const CONST_NAME: u32 = 60 * 60 + 3;
let
- immutable Variable
let x: u8 = 5; // declare variable x and bind to 5
Shadowing
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("inner-scope x is: {x}");
}
println!("outer-scope x is: {x}");
}
inner-scope x is: 12
outer-scope x is: 6
Shadowing to change type
let spaces = " "; // spaces is a string
let spaces = spaces.len(); // spaces is a i32 integer
let mut
- mutable Variable
let mut x: u8 = 5; // bind variable x to 5
x = 6; // bind variable x to 6
fn
Functions
Rust code uses snake_case for function and variables.
main
function
fn main(){
}
dead Functions
#[allow(dead_code)]
fn deadfn(){}
Parameters and Return
// Function with two parameters and a return value
fn add(a: i32, b: i32) -> i32 {
a+b
}
add(5,4)
Default Values
// Function with parameters that have default values
fn add(a: Option<u8>, b: Option<u8>) -> u8 {
a.unwrap_or(1) + b.unwrap_or(2)
}
println!("{}", add(None, None));
println!("{}", add(3, 4));
Function macros
A function call finishing with !
return not a value but code. It is essentially a macro.
let x = 5;
let y = 10;
println!("x = {x} and y + 2 = {}", y + 2);
|x|
Closures
A closure is a type of anonymous function (don't have names) that can capture variables from its surrounding environment. These variables can then be used within the closure's body, even if they are not passed in as parameters.
Closures are normally short and only available in a narrow context. Compiler determines the input and return variable type. The first usage will define the types of the variables.
fn main() {
let x = 5;
let add = |y| x + y;
let result = add(3);
println!("{}", result); // Prints "8"
}
More complete example with a Cacher. The cacher implementation is not finished, it only caches the value for one arg
instead for each arg a new value. It can be fixed with a hashmap.
use std::thread;
use std::time::Duration;
fn expensive_calculation(intensity: u32) -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
intensity
}
fn main() {
let simulated_intensity = 10;
let simulated_random_number = 7;
generate_workout(simulated_intensity, simulated_random_number);
}
// for using struct with closures, generics and trait bounds are needed
struct Cacher<T> // Generic <T>
where
T: Fn(u32 -> u32), // Trait Fn
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>,
where
T: Fn(u32) -> u32,
{
fn new(calculation:T) -> Cacher<T> {
Cacher {
calculation,
value:None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
}
}
}
}
fn generate_workout(intensity: u32, random_number: u32) {
// Closure definition
//let expensive_closure = |num: u32| -> u32 { // with type definition
let expensive_closure = |num| { // without type definition
println!("calculatin slowly");
thread::sleep(Duration::from_secs(2));
num
};
// Closure definition with Cacher struct
let mut expensive_cacher = Cacher::new(|num| { // without type definition
println!("calculatin slowly");
thread::sleep(Duration::from_secs(2));
num
};
if intensity < 25 {
println!(
"Today, do {} pushups!",
//expensive_calculation(intensity) // get value from function
//expensive_closure(intensity) // get value from closure
expensive_cacher.value(intensity) // get value from Cacher with closure
);
println!(
"Next, do {} situps!",
//expensive_calculation(intensity) // get value from function
//expensive_closure(intensity) // get value from closure
expensive_cacher.value(intensity) // get value from Cacher with closure
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
//expensive_calculation(intensity) // get value from function
//expensive_closure(intensity) // get value from closure
expensive_cacher.value(intensity) // get value from Cacher with closure
);
}
}
}
Capture variables with closures
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
Closures can capture values form their environment in three way (same as functions): 1. Taking ownwership (FnOnce
trait) 2. Borrow mutable (FnMut
trait) 2. Borrow immutable (Fn
trait)
Flow Control
if
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");
}
can also be used for variables
let condition = true;
let number = if condition {5} else {6};
if let
if let
is similar to match but is not exhaustive. All others values
are ignored.
fn main() {
let some_value = Some(3);
match some_value {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = some_value { // comparison inversed
println!("three");
}
}
for
for _ in 0..10 {
//...
}
for _ in 0..=10 {
//...
}
Use .rev()
to reverse the list range,
for nbr in (0..=5).rev() {
println!("{nbr} ...");
}
println!("LIFTOFF!!!");
//ok but not recommended because of performance and safety
let collection = [1, 2, 3, 4, 5];
for i in 0..collection.len() {
let item = collection[i];
//...
}
Access: Ownership
-
Shorthand
for item in collection
-
Equivalent to
for item in IntoIterator::into_iter(collection)
Access: Read-Only
-
Shorthand
for item in &collection
-
Equivalent to
for item in collection.iter()
Access: Read-Write
-
Shorthand
for item in &mut collection
-
Equivalent to
for item in collection.iter_mut()
while
let mut samples = vec![];
while samples.len() < 10 {
let sample = take_sample();
if is_outlier(sample) {
continue;
}
samples.push(sample);
}
iterate over collection:
let arr = [10, 20, 30, 40, 50];
let mut idx = 0;
while idx < 5 {
println!("the value is: {}", arr[idx]);
idx += 1;
}
loop
in Rust loop
is preferred over while
.
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
continue
continue
a llows to skip the rest of the interation.
for n in 0..10 {
if n % 2 == 0 {
continue;
}
//...
}
break
break
can be used to exit the current loop or to exit a specific loop with break '<label>;
. The break
keyword also return a value, by default ()
or what is defined e.g. break 123;
'outer: for x in 0.. {
for y in 0.. {
for z in 0.. {
if x + y + z > 1000 {
break 'outer;
}
//...
}
}
}
match
match
is perform an action depeding on the value / variant. A match
needs to have an arm for all possibilities.
fn main() {
let some_value = Some(3);
match some_value {
Some(3) => println!("three");
_ => (),
}
}