Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

Learn how to parse arguments

In order to interact with our cli, we need to provide and retrieve arguments

🎯 Objectives

  • interact with environnement
  • add dependencies
  • parse arguments
  • manage errors
  • use modules
  • type conversion

📝 Retrieve cli arguments

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 with cargo run program options (ex: cargo run --help)
    Equivalent to ./target/debug/crabby foo bar 1
  • Arguments collected are Strings

💡 Common libraries provided in Rust standard library

📚 More resources

📝 Manage Errors

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 backtrace

We need to manage errors gracefully

Vectors 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 :

Retrieve application argument

Try to pass an integer as argument and print square

cargo run -- 2

What 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 this

Arguments passed to app are Strings, you need to cast String to integer

Parse application argument

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

Type conversion

When you want to match a type to another, Rust provided trait to implement custom behaviour

From trait

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 From trait you don't have to handle equivalent Into trait

TryFrom trait

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

📝 Exercice : Update application to execute two commands

Greetings

cargo run greets You

Hello, You 🦀 !

Chifoumi

cargo run chifoumi rock paper

p1 vs p2 : Lost

Read the compiler errors :)

💡 Tips

  • String can be created from &str with String::from()
  • &str can be created from String with .as_str()̀
  • use type conversion to convert cli argument to Game enum

📚 More resources

👏 Congrats

You understand how to interact with your cli !

Check a solution with unit tests here

📝 Summary

What you have learned

  • use module
  • manage errors
  • parse type
  • convertion with traits

Next Part

🤙 Go to next part: modules