Enjoy Day 6

We should use Composition over Inheritance and here's how to do it.

Supertraits

We can compose trait like this 👇

trait Human {
    fn name(&self) -> String;
}

trait Learner: Human {
    fn is_enjoy(&self) -> bool;
}

trait Coder {
    fn get_language(&self) -> String;
}

trait Rustaceans: Coder + Learner {
    fn get_blog(&self) -> String;
}

struct Me {}
impl Human for Me {
    fn name(&self) -> String {
        "katopz".to_owned()
    }
}
impl Learner for Me {
    fn is_enjoy(&self) -> bool {
        true
    }
}
impl Coder for Me {
    fn get_language(&self) -> String {
        "rust".to_owned()
    }
}
impl Rustaceans for Me {
    fn get_blog(&self) -> String {
        "https://katopz.medium.com/".to_owned()
    }
}

fn greeting_rustaceans(someone: &dyn Rustaceans) -> String {
    format!(
        "My name is {}, I {} coding in {}, you can visit my blog at {}.",
        someone.name(),
        someone.is_enjoy().then(|| "enjoy").unwrap_or("sad"),
        someone.get_language(),
        someone.get_blog(),
    )
}

fn main() {
    println!("{}", greeting_rustaceans(&Me {}));
}

💡 More about Supertraits


We can use generic to make a boundary what we accept, see example below.

Generic Bounds

use std::ops::Deref;

trait Human {
    fn name(&self) -> String;
}

trait Learner: Human {
    fn is_enjoy(&self) -> bool;
    fn increase_power(&mut self, amount: u8);
}

trait Coder {
    fn get_language(&self) -> &str;
}

trait Rustaceans: Coder + Learner {
    fn get_blog(&self) -> String;
}

#[derive(Debug, Default)]
struct Me {
    language: String,
    power: u8,
}

impl Me {
    // We derived Default so we need to impl this fn default().
    fn default() -> Self {
        Me {
            power: 0u8,
            language: "🦀 Rust".to_owned(),
        }
    }

    // Here's getter/setter.
    fn set_power(&mut self, amount: u8) {
        self.power = amount
    }
}

impl Human for Me {
    fn name(&self) -> String {
        "katopz".to_owned()
    }
}
impl Learner for Me {
    fn is_enjoy(&self) -> bool {
        true
    }

    fn increase_power(&mut self, amount: u8) {
        self.set_power(self.power + amount);
    }
}
impl Coder for Me {
    fn get_language(&self) -> &str {
        // We need to 👇 deref so👆 we can return &str.
        self.language.deref()
    }
}
impl Rustaceans for Me {
    fn get_blog(&self) -> String {
        "https://katopz.medium.com/".to_owned()
    }
}

struct You {}

// You are just ordinary Human.
impl Human for You {
    fn name(&self) -> String {
        "foo".to_owned()
    }
}

// This👇 T = Type mean this fn will accept Learner Type = Generic Bounds.
fn learn<T: Learner>(t: &mut T) {
    // And can be reuse here 👆.
    t.increase_power(9u8);
}

// We can compose type 👇 with this 👇 = Multiple bounds.
fn join_hackathon<T: Human + Learner>(t: &mut T, amount: u8) {
    t.increase_power(amount);
}

// Or use compose traits as parameters like this
fn enjoy_rust(t: &mut (impl Learner + Coder)) {
    t.increase_power(11u8);
}

// Or dynamic like this
fn blog_rust(t: &mut dyn Rustaceans) {
    t.increase_power(12u8);
}

fn main() {
    let mut me = Me::default();

    // Learn lonely
    learn(&mut me);
    println!("1️⃣ {:?}", me);

    // Join hackathon
    join_hackathon(&mut me, 10u8);
    println!("2️⃣ {:?}", me);

    // 😱 Uncomment below to see `the trait bound `You: Learner` is not satisfied`.
    // join_hackathon(&mut You {}, 100u8);

    // Enjoy!
    enjoy_rust(&mut me);
    println!("3️⃣ {:?}, enjoy {}", me, Coder::get_language(&me));

    // We can do anything. Yeah!
    blog_rust(&mut me);
    println!("4️⃣ {:?}, blog {}", me, Rustaceans::get_blog(&me));
}

💡 More about Generics-Bounds


Parameterize traits

We can make trait parameters generic like this 👇.

// Define a trait called `Combiner` that takes three generic type parameters: A, B, and C.
trait Combiner<A, B, C> {
    // Declare an associated function `combine`.
    fn combine(a: &A, b: &B) -> C;
}

// String + String = String
struct StringCombiner;

impl Combiner<String, String, String> for StringCombiner {
    fn combine(a: &String, b: &String) -> String {
        format!("{}{}", a, b)
    }
}

// i32 + i32 = i32
struct NumberCombiner;

impl Combiner<i32, i32, i32> for NumberCombiner {
    fn combine(a: &i32, b: &i32) -> i32 {
        *a + *b
    }
}

fn main() {
    // Combine string.
    let str1 = String::from("Hello, ");
    let str2 = String::from("world!");

    let str_result = StringCombiner::combine(&str1, &str2);
    println!("String result: {}", str_result);

    // Combine number
    let num1 = 5;
    let num2 = 10;

    let num_result = NumberCombiner::combine(&num1, &num2);
    println!("Number result: {}", num_result);
}

Look handy!

Continue to Day 7 ➠