Enjoy Day 7

Sometime compiler didn't know what size (and type) we will return so Box and dyn is here to help.

Oh, it's just like we put something in the 📦 so mister postman(compiler) can manage them properly!

#![allow(unused)]
fn main() {
let rust = "Rust".to_string()
let us = &rust[1..=2];
}
StackHeapStringstrownsptrRustlen4capacity4&strborrowsptrlen2StackRustus

Imagine each R u s t is super car...

It would be nice if we can borrow & instead of buying a new one!

Dynamic Dispatch with Box, dyn

// ...Continue from previous example.
#[derive(Debug, Clone)]
struct Animal {}
struct Human {}

trait Sayable {
    fn say(&self) -> String;
}

impl Sayable for Animal {
    fn say(&self) -> String {
        "meow!".to_owned()
    }
}

impl Sayable for Human {
    fn say(&self) -> String {
        "hi!".to_owned()
    }
}

// Compiler'll need this👇 Box to know its size (Box's size).
fn animal_or_human() -> Box<dyn Sayable> {
    // Compiler'll need this 👆 dyn to know it'll be dynamic (Animal or Human)

    // How to get current time.
    let now = std::time::SystemTime::now();

    // How to get duration since UNIX_EPOCH.
    let result_duration = now.duration_since(std::time::UNIX_EPOCH);

    // How to convert `Result` to `Option`.
    let maybe_duration = result_duration.ok();

    match maybe_duration {
        Some(duration) => {
            // Take secs
            let sec = duration.as_secs();

            // Modulo so we get 50% chance randomly by current time.
            if sec % 2 == 0 {
                Box::new(Animal {})
            } else {
                Box::new(Human {})
            }
        }
        // When you not finish implementation yet, try use todo!.
        None => todo!(),
    }
}

fn main() {
    // Randomly say by animal or human.
    println!("{:?}", animal_or_human().say());
}

We call this -> Box<dyn Sayable> as Dynamic Dispatch which have some little overhead compare to -> impl Sayable (static dispatch).


Let's try avoid Dynamic Dispatch with Static Dispatch by enum_dispatch

Static dispatch with enum_dispatch

// ...Continue from previous example.
#[derive(Debug, Clone)]
struct Human {}
struct Animal {}

trait Sayable {
    fn say(&self) -> String;
}

impl Sayable for Animal {
    fn say(&self) -> String {
        "meow!".to_owned()
    }
}

impl Sayable for Human {
    fn say(&self) -> String {
        "hi!".to_owned()
    }
}

use enum_dispatch::enum_dispatch;
use rand::Rng;

#[enum_dispatch]
enum SayableEnum {
    Animal,
    Human,
}

fn animal_or_human() -> SayableEnum {
    if rand::thread_rng().gen_range(0u8..9u8) % 2 == 0 {
        Animal {}.into()
    } else {
        Human {}.into()
    }
}

fn main() {
    // Randomly say by animal or human.
    println!(
        "{:?}",
        match animal_or_human() {
            SayableEnum::Animal(e) => e.say(),
            SayableEnum::Human(e) => e.say(),
        }
    );
}

Or avoid Dynamic Dispatch with Static Dispatch by either

Static Dispatch with either

// ...Continue from previous example.
#[derive(Debug, Clone)]
struct Human {}
struct Animal {}

trait Sayable {
    fn say(&self) -> String;
}

impl Sayable for Animal {
    fn say(&self) -> String {
        "meow!".to_owned()
    }
}

impl Sayable for Human {
    fn say(&self) -> String {
        "hi!".to_owned()
    }
}

use either::Either;
use rand::Rng;

// Can be either Sayable or Sayable.
fn animal_or_human() -> Either<impl Sayable, impl Sayable> {
    // Use random number instead of time, just for fun.
    if rand::thread_rng().gen_range(0u8..9u8) % 2 == 0 {
        Either::Left(Animal {})
    } else {
        Either::Right(Human {})
    }
}

fn main() {
    // Randomly say by animal or human.
    let either_animal_or_human = animal_or_human();
    println!(
        "{:?}",
        match either_animal_or_human.is_left() {
            true => either_animal_or_human.left().unwrap().say(),
            false => either_animal_or_human.right().unwrap().say(),
        }
    );
}

At this point you should able to read a lot of Rust code out there, let's teardown ➠.