Type Params

Параметризованные типажи

Подготовим пример: есть рыцарь и маг, оба могут держать оружие, но оружие у каждого своё.

// Define a weapon type
struct Sword {
    damage: u32,
}

struct Wand {
    magic_power: u32,
}

// This trait says "I can hold some type of item W"
// The <W> is a placeholder waiting to be filled
trait CanHold<W> {
    fn hold(&self, item: W);
    fn describe_held_item(&self) -> String;
}

В данном случае c параметром типаж как пустой стикер или пустой знак отличия, где поле ещё не заполнено конкретикой. Заполним конкретику:

struct Knight {
    name: String,
}

struct Wizard {
    name: String,
}

// The knight can hold a Sword specifically
impl CanHold<Sword> for Knight {
    fn hold(&self, item: Sword) {
        println!("{} grips the sword firmly! Damage: {}", self.name, item.damage);
    }
    
    fn describe_held_item(&self) -> String {
        String::from("A heavy blade")
    }
}

// The wizard can hold a Wand specifically
impl CanHold<Wand> for Wizard {
    fn hold(&self, item: Wand) {
        println!("{} twirls the wand gracefully! Power: {}", self.name, item.magic_power);
    }
    
    fn describe_held_item(&self) -> String {
        String::from("A mystical wand")
    }
}

Теперь добавим, что рыцарь может держать в руках ещё щит - и это тоже попадает в типаж, остаётся лишь +1 параметр добавить:

struct Shield {
    defense: u32,
}

// The knight can ALSO hold a Shield!
impl CanHold<Shield> for Knight {
    fn hold(&self, item: Shield) {
        println!("{} raises the shield! Defense: {}", self.name, item.defense);
    }
    
    fn describe_held_item(&self) -> String {
        String::from("A sturdy shield")
    }
}

fn main() {
    let knight = Knight { name: String::from("Arthur") };
    
    // The same knight can hold different items!
    knight.hold(Sword { damage: 50 });   // Uses CanHold<Sword>
    knight.hold(Shield { defense: 30 }); // Uses CanHold<Shield> - same trait, different weapon!
}

Без параметра типаж работает НЕ БУДЕТ: потому что нельзя тот же типаж CanHold несколько раз применять просто так. А с параметром - МОЖНО, главное, чтобы параметр W отличался. Тогда один рыцарь может иметь несколько знаков из одной семьи знаков.

Пример на базе AsRef

Рассмотрим пример работы со строками из статьи

// AsRef is defined like this (simplified):
trait AsRef<T> {
    fn as_ref(&self) -> &T;  // "Turn myself into a reference to T"
}

// String implements AsRef<str> - "I can act as a string slice reference"
impl AsRef<str> for String {
    fn as_ref(&self) -> &str {
        // String can easily give a &str view of its contents
        &self[..]
    }
}

// &str also implements AsRef<str> - "I'm already a str reference!"
impl AsRef<str> for &str {
    fn as_ref(&self) -> &str {
        self  // Just return myself, I'm already what you want
    }
}

// PathBuf (a file path type) implements AsRef<Path>
impl AsRef<Path> for PathBuf {
    fn as_ref(&self) -> &Path {
        // Give a Path view of the PathBuf
        self.as_path()
    }
}

Теперь когда мы пишем:

fn hello<N: AsRef<str>>(name: N) {
    println!("Hello, {}!", name.as_ref());
}

то это значит “приму любой тип N, если у N есть знак/стикер AsRef”, и это означает, что N может превратиться в &str.

Пример с превращениями

Представим игру, где персонажи могут превращаться в драконов или зайцев:

struct Player {
    name: String,
}

struct Dragon {
    fire_power: u32,
}

struct Rabbit {
    speed: u32,
}

// The polymorph trait: "I can transform into T"
trait Polymorph<T> {
    fn transform(&self) -> T;
}

// Player can polymorph into a Dragon
impl Polymorph<Dragon> for Player {
    fn transform(&self) -> Dragon {
        println!("{} grows scales and wings!", self.name);
        Dragon { fire_power: 100 }
    }
}

// Player can ALSO polymorph into a Rabbit
impl Polymorph<Rabbit> for Player {
    fn transform(&self) -> Rabbit {
        println!("{} shrinks and grows fluffy ears!", self.name);
        Rabbit { speed: 200 }
    }
}

fn main() {
    let player = Player { name: String::from("Alex") };
    
    // Same player, same trait name, completely different transformations!
    let dragon_form = player.transform(); // Rust infers we want Dragon here
    let rabbit_form: Rabbit = player.transform(); // Explicitly ask for Rabbit
}