Manipulate Rust syntax needed for our CLI
- variable declaration and assignation
- basic types
- compound types
- operator
- statement vs expression
- unit test
Let discover some Rust Syntax and concepts
💡 Notes
Take time to read this part
You can refer to it when writing exercice implementation
Everything needed is described below.
let x; // declare "x"
x = 10; // assign 10 to "x"
// declare with assign
let x = 10; // The default integer type in Rust is i32
let y: i8 = -128;
let x = 1.5; // the default float type in Rust is f64
let y: f64 = 2.0;
//cast
let x: i32 = -1200
let y: u16 = 6547
let res: f64 = x as f64 / y as f64
// two equivalent syntaxes
let a = 5i8;
let a : i8 = 5;
// two equivalent syntaxes
let b = 100_000_000;
let b = 100000000;
// casting
let foo = 3_i64;
let bar = foo as i32;
// no changing value
const FOREVER_AGE: u8 = 18;
static LANGUAGE: &str = "Rust";
// mutability
let age = 5; // declare variable
age = 18 //boom, variables are immutable by default
let mut age = 6; // add mut keyword
age = 5; // ok
print!("display line without newline")
println!("display with placeholders {} and new line", 1);
// pattern options
{}: std::fmt::Display trait
{:?} : std::fmt::Debug trait
{:p} : format the variable as a pointer and prints the memory address that the value points to.
{:032b} means to format as a binary via the std::fmt::Binary trait with 32 zeroes padded on the left
// syntax to specifiy numbers
123_456 // underscore as separator
0x12 // prefix 0x to indicate hex value
0o23 // prefix 0o to indicate octal value
0b0001 // prefix 0b to indicate binary value
b'a' // A single byte character
// example with formatting print
println!("Base 10 repr: {}", 69420);
println!("Base 2 (binary) repr: {:b}", 69420);
println!("Base 8 (octal) repr: {:o}", 69420);
println!("Base 16 (hexadecimal) repr: {:x}", 69420);
println!("Base 16 (hexadecimal) repr: {:X}", 69420);
📌 Remember
- variables are immutable if no
m̀ut
keyword specified - type can be explicit or infered when possible for compiler
- println! can format types to text
📚 More resources
// String literals, not mutable
let x: &str = "hello world!"; // note lowercase syntax "str"
// String
let s: String = "Hello world".to_string(); // camelCase syntax
// another way to build String
let s: String = String::from("Hello world"); //
// concatenation needs mutable String
let mut hello = String::from("Hello, ");
hello.push('w');
hello.push_str("orld!");
// string block
let json = r#"
{
"name": "George",
"age": 27,
"verified": false
}
"#;
📌 Remember
- Strings literals are not mutable (stored on stack)
- Strings is more conveniant than &str but less "performant" (stored on heap)
📚 More resources
// A fixed-size array of four i32 elements
let mut four_ints: [i32; 4] = [1, 2, 3, 4];
four_ints[4] = 9 // boom, index of bound, cannot extend size
// A dynamic array (vector)
let mut vector: Vec<i32> = vec![1, 2, 3, 4]; // vec! is a macro
vector.push(5); // ok, vector have no fixed size
//tuples
let x: (i32, &str, f64) = (1, "hello", 3.4);
// Destructuring `let`
let (a, b, c) = x;
println!("{} {} {}", a, b, c); // 1 hello 3.4
📌 Remember
- array have fixed size
- vector have dynamic size (and many methods to manipulate data)
- destructing is commonly used in match
- functions with
!
likevec![]
are macro. Rust replace it with code at compilation
📚 More resources
// Struct
struct Point {
x: i32,
y: i32,
}
let origin: Point = Point { x: 0, y: 0 };
// A struct with unnamed fields, called a ‘tuple struct’
struct Point2(i32, i32);
let origin2 = Point2(0, 0);
// enum
enum Direction {
Right,
Left,
Up,
Down,
}
let left = Direction::Left;
// enum with values
enum Movement {
Right(i32),
Left(i32),
Up(i32),
Down(i32),
}
// enum with struct values
enum Actions {
StickAround,
MoveTo { x: i32, y: i32},
}
📌 Remember
- Rust do not mix data and behaviour. You don't have "classes" like in Java
enum
are powerful and commonly used withmatch
operator
📚 More resources
Rust have some enum already defined Option
and Result
// An output can have either Some value or no value/ None.
enum Option<T> { // T is a generic and it can contain any type of value.
Some(T),
None,
}
// retrieve an element in collection can be Some or None
let v = vec![10, 20, 30]; // initialization macro
let idx = 0;
match v.get(idx) {
Some(value) => println!("Value is {}", value),
None => println!("No value..."),
}
// A result can represent either success/ Ok or failure/ Err.
// T and E are generics. T can contain any type of value, E can be any error.
enum Result<T, E> {
Ok(T),
Err(E),
}
// try to convert string to integer can fail
let num = "10";
match num.parse::<i32>() {
Ok(value) => println!("Num is an integer {}", value),
Err(e) => println!("Not an integer... {}", e),
}
📌 Remember
- There are no Exception in Rust. Either you have a successful operation or an Error
- There are non Null or Void in Rust. Either you have a value or an absence of value
📚 More resources
fn is_even(i: i32) -> bool {
i % 2 == 0
}
// fizzbuzz
if x % 3 == 0 && x % 5 == 0 {
println!("FizzBuzz")
} else if x % 3 == 0 {
println!("Fizz")
} else if x % 5 == 0 {
println!("Buzz")
} else {
println!("{}", x)
}
// The empty tuple () represents the absence of data.
fn whatever() -> () {}
// pattern matching
match (self % 3, self % 5) {
(0, 0) => String::from("FizzBuzz"),
(0, _) => String::from("Fizz"),
(_, 0) => String::from("Buzz"),
_ => format!("{}", self),
}
let sum: i32 =
(0..5) // this is an iterator
.filter(|i| is_even(*i)) // filter with a closure
.sum(); // consume the iterator
println!("sum of even numbers is {}", sum);
//nb: vector is not an iterator
let numbers = vec![1,2,3,4];
let even = numbers.into_iter() //get iterator from collection
.filter(|index| index % 2 == 0) // only keep even
.collect::<Vec<i32>>(); //collect into vector with explicit type
📌 Remember
- statements need
;
keyword - note the absence of
return whatever ;
keyword when evaluating expression - match operator evaluates all possible values
📚 More resources
// main.rs
// code stripped
fn add(a: u8, b: u8 ) -> u8 {
a + b
}
add tests module in your main file
// main.rs
// code stripped
#[cfg(test)]
mod tests {
use super::add;
#[test]
fn test_add() {
assert_eq!(add(12, 5), 17);
}
}
Run your first test with cargo
cargo test
💡 Notes
#[..]
annotation is aderive attribute
(another macro likeprintln!
)mod
define a new modulescfg(test)
indicates that this part of code is only used in testing context
🎉 Congrats, you have written your first Unit test !
Let make some changes in our previous function
Change signatures and launch test
👉 Check cargo test
output
|
| assert_eq!(add(300, 5), 17);
| ^^^
|
= note: `#[deny(overflowing_literals)]` on by default
= note: the literal `300` does not fit into the type `u8` whose range is `0..=255`
Change first argument signature with i64
and launch test
👉 Check cargo test
output
error[E0308]: mismatched types
--> 2_syntax/solution/src/main.rs:6:9
|
6 | a + b
| ^ expected `i64`, found `u8`
error[E0308]: mismatched types
--> 2_syntax/solution/src/main.rs:6:5
|
5 | fn add(a: i64, b: u8) -> u8 {
| -- expected `u8` because of return type
6 | a + b
| ^^^^^ expected `u8`, found `i64`
|
help: you can convert an `i64` to a `u8` and panic if the converted value doesn't fit
|
6 | (a + b).try_into().unwrap()
| + +++++++++++++++++++++
error[E0277]: cannot add `u8` to `i64`
--> 2_syntax/solution/src/main.rs:6:7
|
6 | a + b
| ^ no implementation for `i64 + u8`
|
= help: the trait `Add<u8>` is not implemented for `i64`
= help: the following other types implement trait `Add<Rhs>`:
<&'a f32 as Add<f32>>
<&'a f64 as Add<f64>>
<&'a i128 as Add<i128>>
<&'a i16 as Add<i16>>
<&'a i32 as Add<i32>>
<&'a i64 as Add<i64>>
<&'a i8 as Add<i8>>
<&'a isize as Add<isize>>
and 48 others
Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `crabby_syntax` due to 3 previous errors
Rust compiler warning and hints help you detect and fix problems very fast
💡 Notes
- Compiler is really an helping tools that explicits reason of failure
- Most of the time, it will give a solution or hint and a link to related documentation
- some consider rust compiler as your pair
Write Chifoumi game logic (Rock, Paper, Scissors)
- Rock beats scissors
- Scissors beats paper
- Paper beats rock
Write the missing code
#[derive(Debug)]
enum Game {
//your code
}
#[derive(Debug, PartialEq)]
enum GameResult {
Win,
Lost,
Draw
}
fn play( a: ?, b: ?) -> GameResult {
// your code
}
fn main() {
// define your games a and b
// call play function with arguments
// display result
}
💡 Notes
#[derive(Debug)]
: asks the compiler to auto-generate a suitable implementation of the Debug trait to display Struct#[derive(PartialEq)]
: asks the compiler to enables use of the == and != operator by implementin 'PartialEq' trait (Useful in trait)
Display variable without custom implementation, you can add #derive(Debug)̀
println!("Debug {:?}", variable);
👉 Implementation Tips
Instead of using if/else to compare values, you can use match operator// make a tuple with both values
match (a, b) {
(Game::Rock, Game::?) => ... code ...
}
using enum, you cannot miss matching cases without compiler warning
//compiler error example
error[E0004]: non-exhaustive patterns: `(?, ?)` not covered
You understand enough syntax and Rust Concepts to code a chifoumi
Check a solution with unit tests here
What you have learned
- declare and assign variables
- express how to store data with primitives or custom types
- define functions
- write and launch unit test