In order to interact with our cli, we need to provide and retrieve arguments
- interact with environnement
- add dependencies
- parse arguments
- manage errors
- use modules
- type conversion
Rust already imports common used symbols like Vec, String, Option and Result.
For other symbols, you must import module with use keyword
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}$ cargo run
Running `/your/path/target/debug/crabby`
["/your/path/target/debug/crabby"]
$ cargo run -- foo bar 1
Running `/your/path/target/debug/crabby`
["/your/path/target/debug/crabby", "foo", "bar", 1]📌 Remember
- every arguments after
--are passed to the program.
Avoid mixing argument withcargo runprogram options (ex: cargo run --help)
Equivalent to./target/debug/crabby foo bar 1 - Arguments collected are Strings
💡 Common libraries provided in Rust standard library
- fmt: formatting and printing
- env: inspect and manipulate process environment
- io: input and output
- path: working with path abstraction
- fs: filesystem
📚 More resources
Update our code to greets with argument passed to our app
fn main() {
let args: Vec<String> = env::args().collect();
greets(&args[1])
}
fn greets(name: &str) {
println!("Hello, {} 🦀 !", name);
}$ cargo run -- John
Running `/your/path/target/debug/crabby_args john`
Hello, john 🦀 !What happens if no argument is provided ?
👉 Check by yourself to see error
$ cargo run --
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1'
note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceVectors implements get api to retrieve element by position (documentation)
// from api doc
let v = [10, 40, 30];
assert_eq!(Some(&40), v.get(1));
assert_eq!(Some(&[10, 40][..]), v.get(0..2));
assert_eq!(None, v.get(3));
assert_eq!(None, v.get(0..4));Either we have an argument provided either none
Optional can have a value Some(val) or None
enum Option<T> {
Some(T),
None,
}Many ways to manage our code
// manage both cases
match args.get(1) {
Some(name) => greets(name),
None => panic!("No name provided!"), //quit program
}
// if / let
if let Some(name) = args.get(1) {
greets(name);
} else {
eprintln!("No name provided");
}
// unwrap
// `unwrap` returns a `panic` when it receives a `None`.
let name = args.get(1).unwrap();
// expect
// panic like unwrap but with message
let name = args.get(1).expect("Name is required");💡 More about flow of control :
Try to pass an integer as argument and print square
cargo run -- 2What happens when we execute this code ?
let x: u32 = args.get(1).unwrap();
println!("{} square is {}", x , x*x );👉 Check by yourself to see error
let x: u32 = args.get(1).unwrap();
| --- ^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `&String`
| |
| expected due to thisArguments passed to app are Strings, you need to cast String to integer
Try to parse String into integer can fail
We can either have a Succes either an error
String parse method returns Result:
enum Result<T, E> {
Ok(T),
Err(E),
}Examples of usage
// Ok
let four: u32 = "4".parse().unwrap(); // explicit type annotation
let four = "4".parse::<u32>().unwrap(); // same but with turbofish annotation
//Err
let notaninteger = "whatever".parse::<u32>().unwrap();
let toomuch = "300".parse::<u8>().unwrap();We can manage error like we do with Option
Nb: Err provides message about failure
match args.get(1).unwrap().parse::<u32>() {
Ok(x) => println!("square of {} is {}", x, x * x),
Err(e) => println!("error parsing : {}", e),
}💡 Notes
When you want to match a type to another, Rust provided trait to implement custom behaviour
Use can From trait when you assume conversion must success
use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
fn main() {
let num = Number::from(30);
println!("My number is {:?}", num);
}If you handle
Fromtrait you don't have to handle equivalentIntotrait
Use tryFrom trait when conversion can fail (you handle this case)
It uses Result enum type
// example to convert an integer to a custom struct
struct GreaterThanZero(i32);
impl TryFrom<i32> for GreaterThanZero {
type Error = &'static str;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value <= 0 {
Err("GreaterThanZero only accepts value superior than zero!")
} else {
Ok(GreaterThanZero(value))
}
}
}
// Returns an error because `big_number` is too big to
// fit in an `i32`.
let try_smaller_number = i32::try_from(big_number);
assert!(try_smaller_number.is_err());
// Returns `Ok(3)`.
let try_successful_smaller_number = i32::try_from(3);
assert!(try_successful_smaller_number.is_ok());📌 Remember
- You must handle conversion yourself with traits
- you decide if failure ends your program or not
💡 notes
Greetings
cargo run greets You
Hello, You 🦀 !Chifoumi
cargo run chifoumi rock paper
p1 vs p2 : LostRead the compiler errors :)
💡 Tips
Stringcan be created from&strwithString::from()&strcan be created from String with.as_str()̀- use type conversion to convert cli argument to Game
enum
📚 More resources
You understand how to interact with your cli !
Check a solution with unit tests here
What you have learned
- use module
- manage errors
- parse type
- convertion with traits