Pattern matching

Pattern matching

match SCRUTINEE {
    PATTERN => EXPRESSION,
    PATTERN => {
        EXPRESSION BLOCK
    }
}
let a = 1;

match a {
    1 => println!("One"),
    2 => println!("Two"),
}

match is exhaustive!

let a = 1;

match a {
    1 => println!("One"),
    2 => println!("Two"),
    i32::MIN..=i32::MAX => println!("Neither one nor two")
}
let a = 1;

match a {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Neither one nor two")
}
let a = 1;

match a {
    1 => println!("One"),
    2 => println!("Two"),
    .. => println!("Neither one nor two")
}
let a = 51;

match a {
    1 => println!("One"),
    2 => println!("Two"),
    n => println!("Neither one nor two, but {}", n)
}

matching named variables

let a = 51;

match a {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Neither one nor two, but {}", a)
}

why not just print a directly?

let a = 51;

match a + 1 {
    1 => println!("One"),
    2 => println!("Two"),
    n => println!("Neither one nor two, but {}", n)
}
let a = 51;

match a + 1 {
    1 => println!("One"),
    2 => println!("Two"),
    n => println!("Neither one nor two, but {}", n)
}

// is equivalent to
if a + 1 == 1 {
    println!("One");
} else if a + 1 == 2 {
    println!("Two");
} else {
    println!("Neither one nor two, but {}", a + 1);
}
let a = 51;

match a {
    1..5 => println!("one to four"),
    5..=10 => println!("five to ten"),
    1 | 2 | 3 | 4 => unreachable!(),
    _ => println!("Anything else")
}

ranges, logical operators

match 3 {
    n if n >= 0 => println!("Positive number"),
    n if n < 0 => println!("Negative number")
}

match guards

match 3 {
    n if n >= 0 => println!("Positive number"),
    n if n < 0 => println!("Negative number"),
    _ => ()
}
match 3 {
    n if n >= 0 => println!("Positive number"),
    n if n < 0 => println!("Negative number"),
    _ => unreachable!()
}

if let / while let

let rgb = (255, 255, 255);

if let (255, _, _) = rgb {
    println!("Max red!");
}

// is equivalent to
match rgb {
    (255, _, _) => println!("Max red!"),
    _ => (),
}

if let

while let Pattern = Scrutinee {
    BlockExpression
}
let rgb = (255, 255, 255);


if let (255, _, _) = rgb {
    println!("Max red!");
} else {
    println!("Not max red..");
}

// is equivalent to
match rgb {
    (255, _, _) => println!("Max red!"),
    _ => println!("Not max red..")
}

else can be used with if let

while let Pattern = Scrutinee {
    BlockExpression
}

while let

fn is_divisible_by(val: i64, div: i64) -> bool {
	val / div == 2
}

let mut a = 1;

while let false = is_divisible_by(a, 7) {
     a += 1;
}

println!("{}", a);

Error handling

panic!

  • when a program reaches an unrecoverable state
  • terminates the program and gives feedback
    • unwinds the stack like C++
  • there’s no way to recover *

panic! example

fn main() {
    panic!("Best program ever");
}

panic! example

fn main() {
    let _var = true || panic!("Best program ever");
}

panic! example

fn main() {
    let _var = true && panic!("Best program ever");
}

Result enum

enum Result<T, E> {
    Ok(T),
    Err(E),
}
  • std::result - included in global namespace
    • Result, Ok(T), Err(e)
  • ignoring Result gives compiler warning

Handling errors

  1. properly handle error
  2. ignore error (panic on error)
  3. propagate error up to the caller

1. Properly handle error

use std::fs::File;

fn main() {
    let f = File::open("rust_workshop_notes.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Could not open file: {:?}", error),
    };
}

2. Ignore error (panic on error)

use std::fs::File;

fn main() {
    let f = File::open("rust_workshop_notes.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Could not open file: {:?}", error),
    };
}
use std::fs::File;

fn main() {
    let f = File::open("rust_workshop_notes.txt").unwrap();

    /*let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Could not open file: {:?}", error),
    };*/
}
  • unwrap (panic on error)
  • expect (panic with message)
use std::fs::File;

fn main() {
    let f = File::open("rust_workshop_notes.txt").expect("Could not open file");

    /*let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Could not open file: {:?}", error),
    };*/
}





fn main() {
    let num_str = "one";
    let fallback_num = 0;

    let num: i32 = num_str.parse().unwrap_or(fallback_num);

    println!("{}", num);
}

2. Ignore error (default value on error)

3. Propagate error up to the caller

use std::num::ParseIntError;

type Point = (i32, i32);

fn parse_coordinate(x: &str, y: &str) -> Result<Point, ParseIntError> {
    Ok( (x.parse::<i32>()?, y.parse::<i32>()?) )
}

fn main() {
    let parsed: Point = parse_coordinate("1", "2").unwrap();
    
    println!("{:?}", parsed);
}

3. Propagate error up to the caller

use std::num::ParseIntError;

type Point = (i32, i32);

fn parse_coordinate(x: &str, y: &str) -> Result<Point, ParseIntError> {
    Ok( (x.parse::<i32>()?, y.parse::<i32>()?) )
}

fn main() {
    let parsed: Point = parse_coordinate("one", "two").unwrap();
    
    println!("{:?}", parsed);
}

3. Propagate error up to the caller

use std::num::ParseIntError;

type Point = (i32, i32);
type ParseResult<T> = Result<T, ParseIntError>;

fn parse_coordinate(x: &str, y: &str) -> ParseResult<Point> {
    Ok( (x.parse::<i32>()?, y.parse::<i32>()?) )
}

fn main() {
    let parsed: Point = parse_coordinate("one", "two").unwrap();
    
    println!("{:?}", parsed);
}

3. Propagate error up to the caller

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    match s.parse::<i32>() {
        Ok(i) => Ok(i*2),
        Err(e) => Err(e)
    }
}

fn parse_and_double_2(s: &str) -> Result<i32, ParseIntError> {
    Ok(s.parse::<i32>()? * 2)
}

fn main() {
    println!("{:?}", parse_and_double("2"));
    println!("{:?}", parse_and_double_2("2"));

    println!("{:?}", parse_and_double_2("house"));
    println!("{:?}", parse_and_double_2("2").unwrap());
}

3. Propagate error up to the caller

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    let res = match s.parse::<i32>() {
        Ok(i) => Ok(i*2),
        Err(e) => Err(e)
    };
    
    println!("Inside parse_and_double: {:?}", res);
    res
}

fn parse_and_double_2(s: &str) -> Result<i32, ParseIntError> {
    let res = Ok(s.parse::<i32>()? * 2);
    
    println!("Inside parse_and_double_2: {:?}", res);
    res
}

fn main() {
    parse_and_double("2");
    parse_and_double_2("2");

    parse_and_double("house");
    parse_and_double_2("house");
}

3. Propagate error up to the caller

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    let res = match s.parse::<i32>() {
        Ok(i) => Ok(i*2),
        Err(e) => Err(e)
    };
    
    println!("Inside parse_and_double: {:?}", res);
    res
}

fn parse_and_double_2(s: &str) -> Result<i32, ParseIntError> {
    let res = Ok(s.parse::<i32>()? * 2);
    
    println!("Inside parse_and_double_2: {:?}", res);
    res
}

fn main() {
    parse_and_double("2");
    parse_and_double_2("2");

    parse_and_double("house");
    parse_and_double_2("house");
}

3. Propagate error up to the caller

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    let res = match s.parse::<i32>() {
        Ok(i) => i,
        Err(e) => return Err(e)
    };
    
    let res = Ok(res*2);
    
    println!("Inside parse_and_double: {:?}", res);
    res
}

fn parse_and_double_2(s: &str) -> Result<i32, ParseIntError> {
    let res = s.parse::<i32>()?;
    
    let res = Ok(res*2);
    
    println!("Inside parse_and_double_2: {:?}", res);
    res
}

fn main() {
    parse_and_double("2");
    parse_and_double_2("2");

    parse_and_double("house");
    parse_and_double_2("house");
}

crate anyhow

use anyhow::Error;

fn read_number_file(path: &str) -> Result<u32, Error> {
    let string = std::fs::read_to_string(path)?;
    let number = string.parse()?;
    Ok(number)
}

fn main() {
    let number = read_number_file("my_number.txt").unwrap();
    println!("Number from file: {}", number);
}

Applications vs Libraries

  • Application
    • Error types are not so important
    • Errors will output to stdout/stderr
    • crates: anyhow
  • Library
    • Used by other programs
    • Errors are part of library API
    • crates: snafu, thiserror

The Option enum

 no null, nil, or undefined!

Option enum

enum Option<T> {
    Some(T),
    None,
}
  • std::option::Option - included in global namespace
    • Option, Some(T), None
  • ignoring Option gives compiler warning

C++

std::optional<T>

Rust C++
return None; return std::nullopt;
return Some(foo); return foo;
is_some() operator bool()
has_value()
unwrap() value()
unwrap_or(bar) value_or(bar)

Handling errors options

  1. properly handle error option
  2. ignore error option (panic on error None)
  3. propagate error None up to the caller

1. Properly handle option

struct Satelite {
    distance_from_earth: Option<u32>
}

fn main() {
    let sat = Satelite{distance_from_earth: Some(400)};
    
    let in_orbit = match sat.distance_from_earth {
        Some(d) if d <= 22_223 => true,
        _ => false
    };
    
    println!("In orbit: {}", in_orbit);
}

2. Ignore option (panic on None)

  • unwrap (panic on None)
  • expect (panic with message)
struct Satelite {
    distance_from_earth: Option<u32>
}

fn main() {
    let sat = Satelite{distance_from_earth: Some(400)};
    
    let in_orbit = sat.distance_from_earth.unwrap() <= 22_223;
    
    println!("In orbit: {}", in_orbit);
}

2. Ignore option (panic on None)

  • unwrap (panic on None)
  • expect (panic with message)
struct Satelite {
    distance_from_earth: Option<u32>
}

fn main() {
    let sat = Satelite{distance_from_earth: None};
    
    let in_orbit = sat.distance_from_earth.expect("Distance unknown") <= 22_223;
    
    println!("In orbit: {}", in_orbit);
}

3. Propagate None up to caller

fn adds_five(number: Option<u8>) -> Option<u8> {
    Some(number?.saturating_add(5))
}

fn main() {
    let n: Option<u8> = Some(253);
    
    let n = adds_five(n).unwrap();
    
    println!("{:?}", n);
}
fn adds_five(number: Option<u8>) -> Option<u8> {
    Some(number?.saturating_add(5))
}

fn main() {
    let n: Option<u8> = None;
    
    let n = adds_five(n).unwrap();
    
    println!("{:?}", n);
}

Useful methods

  • Result, Option -> boolean
    • is_ok(), is_err()
    • is_some(), is_none()
  • Result -> Option
    • ok(), err()
use std::fs::File;

fn main() {
    let f = File::open("rust_workshop_notes.txt");
    
    println!("f is_ok: {}", f.is_ok());
    println!("f is_err: {}", f.is_err());
    
    let option = f.ok();
    println!("option: {:?}", option);
    
    println!("option is_some: {}", option.is_some());
    println!("option is_none: {}", option.is_none());
}
// use std::fs::File;

fn main() {
    // let f = File::open("rust_workshop_notes.txt");
    let f: Result<&str, &str> = Ok("some data");
    
    println!("f is_ok: {}", f.is_ok());
    println!("f is_err: {}", f.is_err());
    
    let option = f.ok();
    println!("option: {:?}", option);
    
    println!("option is_some: {}", option.is_some());
    println!("option is_none: {}", option.is_none());
}
use std::num::ParseIntError;

type Point = (i32, i32);

fn parse_coordinate(x: &str, y: &str) -> Result<Point, ParseIntError> {
    Ok( (x.parse::<i32>()?, y.parse::<i32>()?) )
}

fn main() {
    let parsed: Point = parse_coordinate("1", "2").unwrap();
    
    println!("{:?}", parsed);
}

Refactor parse_coordinate to return Option instead of Result



type Point = (i32, i32);

fn parse_coordinate(x: &str, y: &str) -> Option<Point> {
    Some( (x.parse::<i32>().ok()?, y.parse::<i32>().ok()?) )
}

fn main() {
    let parsed: Point = parse_coordinate("1", "2").unwrap();
    
    println!("{:?}", parsed);
}

Refactor parse_coordinate to return Option instead of Result

Copy of Rust

By Hans Elias Bukholm Josephsen