Шаблонни типове, типажи

23 октомври 2018

Административни неща

Една лоша и една добра новина

Административни неща

Една лоша и една добра новина

Административни неща

Една лоша и една добра новина

Административни неща

За домашното:

Административни неща

За домашното:

Административни неща

За домашното:

Административни неща

За домашното:

Преговор

Документация

1 2 3 4 5 6 7 8 9 10 11
/// Add 2 to a number
///
/// # Example
///
/// \`\`\`
/// # use playground::add_two;
/// assert_eq!(add_two(5), 7);
/// \`\`\`
pub fn add_two(n: u32) -> u32 {
    n + 2
}

Преговор

Тестове

1 2 3 4 5 6 7 8 9 10 11
fn add_two(n: u32) -> u32 {
    n + 2
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(add_two(2), 4);
    }
}

Преговор

Атрибути

Атрибути

Документация

1 2 3 4 5 6
/// Add 2 to a number
///
/// # Example
pub fn add_two(n: u32) -> u32 {
    n + 2
}

Атрибути

Документация

1 2 3 4 5 6
#[doc="Add 2 to a number"]
#[doc=""]
#[doc="# Example"]
pub fn add_two(n: u32) -> u32 {
    n + 2
}

Generic Types (Generics)

Generic Types (Generics)

Oбобщени типове

Generic Types (Generics)

Oбобщени типове

Вече сме ги виждали

Generic Types (Generics)

Oбобщени типове

Вече сме ги виждали

Generic Types (Generics)

Oбобщени типове

Вече сме ги виждали

Oбобщени типове

Oбобщени типове

Oбобщени типове

Oбобщени типове

Oбобщени типове

функции

Със знанията събрани до сега

1 2 3 4 5 6 7
fn add_i32(a: i32, b: i32) -> i32 {
    a + b
}

fn add_u8(a: u8, b: u8) -> u8 {
    a + b
}

Oбобщени типове

функции

С обобщени типове

1 2 3
fn add<T>(a: T, b: T) -> T {
    a + b
}

Oбобщени типове

функции

С обобщени типове

1 2 3
fn add<T>(a: T, b: T) -> T {
    a + b
}
error[E0369]: binary operation `+` cannot be applied to type `T`
 --> /src/main_5.rs:5:5
  |
5 |     a + b
  |     ^^^^^
  |
  = note: `T` might need a bound for `std::ops::Add`

Oбобщени типове

функции

С обобщени типове

1 2 3
fn add<T>(a: T, b: T) -> T {
    a + b
}
error[E0369]: binary operation `+` cannot be applied to type `T`
 --> /src/main_5.rs:5:5
  |
5 |     a + b
  |     ^^^^^
  |
  = note: `T` might need a bound for `std::ops::Add`

Ще видим как да оправим грешката малко по-късно в презентацията

Oбобщени типове

структури

Нека разгледаме структурата

1 2 3 4 5 6 7 8 9
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

Oбобщени типове

структури

А какво ще стане, ако опитаме да я създадем по този начин?

1 2 3
fn main() {
    let what_about_this = Point { x: 5, y: 4.0 }; // ??
}

Oбобщени типове

структури

А какво ще стане, ако опитаме да я създадем по този начин?

1 2 3
fn main() {
    let what_about_this = Point { x: 5, y: 4.0 };
}
error[E0308]: mismatched types
 --> /src/main_7.rs:5:44
  |
5 |     let what_about_this = Point { x: 5, y: 4.0 };
  |                                            ^^^ expected integral variable, found floating-point variable
  |
  = note: expected type `{integer}`
             found type `{float}`

Oбобщени типове

структури

Ако искаме да позволим двете координати да са различни типове

1 2 3 4 5 6 7 8 9 10
struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

Oбобщени типове

енумерации

Ето как се дефинира Option:

1 2 3 4
enum Option<T> {
    Some(T),
    None,
}

Oбобщени типове

енумерации

1 2 3 4
enum Message<T, A> {
    Text(T),
    Action(A),
}

Oбобщени типове

методи

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
struct Point<T> { x: T, y: T }

// Забележете impl<T>
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x   = {}", p.x);    // ??
    println!("p.x() = {}", p.x());  // ??
}

Oбобщени типове

методи

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
struct Point<T> { x: T, y: T }

// Забележете impl<T>

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x   = {}", p.x);
    println!("p.x() = {}", p.x());
}
p.x   = 5
p.x() = 5

Oбобщени типове

специализирани имплементации

В този пример само Point<f32> ще притежава този метод

1 2 3 4 5 6
// Този път няма impl<T>
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

Oбобщени типове

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point { x: self.x, y: other.y }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};
    let p3 = p1.mixup(p2);
    println!("p3.x = {}", p3.x);
    println!("p3.y = {}", p3.y);
}
p3.x = 5
p3.y = c

Упражнение

The JSON encoder

1 2 3
fn to_json<T>(val: T) -> String {
    ...
}

Упражнение

The JSON encoder

Упражнение

The JSON encoder

Но как да я имплементираме?

Упражнение

The JSON encoder

Но как да я имплементираме?
Какъв тип да е T? Може да е всичко? Ще правиме проверки дали получаваме число или низ?

Упражнение

The JSON encoder

Но как да я имплементираме?
Какъв тип да е T? Може да е всичко? Ще правиме проверки дали получаваме число или низ?
А ако е наш собствен тип?

Типажи

Traits

Типажи

Traits

Типажи

Traits

Типажи

Traits

Типажи

Traits

Упражнение

The JSON encoder

Нека си дефинираме trait:

1 2 3
trait ToJson {
    fn to_json(&self) -> String;
}

Упражнение

The JSON encoder

Сега можем да го имплементираме за някои вградени типове данни:

1 2 3 4 5
impl ToJson for String {
    fn to_json(&self) -> String {
        format!("\"{}\"", self)
    }
}

Упражнение

The JSON encoder

Сега можем да го имплементираме за някои вградени типове данни:

1 2 3 4 5 6 7 8 9 10 11
impl ToJson for i32 {
    fn to_json(&self) -> String {
        format!("{}", self)
    }
}

impl ToJson for f32 {
    fn to_json(&self) -> String {
        format!("{}", self)
    }
}

Упражнение

The JSON encoder

1 2 3
println!("String as json: {}", String::from("mamal").to_json());

println!("Number as json: {}", 3.to_json());
String as json: "mamal"
Number as json: 3

Упражнение

The JSON encoder

Можем да имаме имплементация по подразбиране:

1 2 3 4 5 6 7 8 9 10 11
trait ToJson {
    fn to_json(&self) -> String {
        String::from("null")
    }
}

impl ToJson for () {}

fn main() {
    println!("Unit as json: {}", ().to_json());
}
Unit as json: null

Упражнение

The JSON encoder

Още малко - за Option!

1 2 3 4 5 6 7 8
impl<T> ToJson for Option<T> where T: ToJson {
    fn to_json(&self) -> String {
        match self {
            &Some(ref val) => val.to_json(),
            &None => String::from("null"),
        }
    }
}

Упражнение

The JSON encoder

В JSON има списъци, нека да пробваме да го направим за вектор:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
impl <T> ToJson for Vec<T> where T: ToJson {
    fn to_json(&self) -> String {
        let mut iter = self.iter();
        let first = iter.next();
        let mut result: String = first.to_json();

        for e in iter {
            result.push_str(", ");
            result.push_str(&e.to_json());
        }

        format!("[{}]", result)
    }
}

Упражнение

The JSON encoder

В JSON има списъци, нека да пробваме да го направим за вектор:

1 2 3 4
fn main() {
    let arr = vec![Some(1.1), Some(2.2), None].to_json();
    println!("Vector as json: {}", arr);
}
Vector as json: [1.1, 2.2, null]

Упражнение

The JSON encoder

А сега и за наш си тип:

1 2 3 4 5 6
struct Student {
    age: i32,
    full_name: String,
    number: i32,
    hobby: Option<String>
}

Упражнение

The JSON encoder

1 2 3 4 5 6 7 8 9 10 11 12 13 14
impl ToJson for Student {
    fn to_json(&self) -> String {
        format!(
r#"{{
    "age": {},
    "full_name": {},
    "number": {},
    "hobby": {}
}}"#,
            self.age.to_json(), self.full_name.to_json(),
            self.number.to_json(), self.hobby.to_json()
        )
    }
}

Упражнение

The JSON encoder

1 2 3 4 5 6 7 8 9 10
fn main() {
    let student = Student {
        age: 16,
        full_name: "Jane Doe".to_owned(),
        number: 5,
        hobby: Some("Tennis".to_string())
    };

    println!("{}", student.to_json());
}
{
    "age": 16,
    "full_name": "Jane Doe",
    "number": 5,
    "hobby": "Tennis"
}

Упражнение

The JSON encoder

Сега можем да си дефинираме функцията, от която почна всичко:

1 2 3
fn to_json<T: ToJson>(value: T) -> String {
    value.to_json()
}

Типажи

Traits

А ако искаме дадена стойност да имплементира повече от един trait?

1 2 3
fn log_json_transformation<T: ToJson + Debug>(value: T) {
    println!("{:?} -> {}", value, value.to_json());
}

Traits

Кога можем да имлементираме trait?

Traits

Кога можем да имлементираме trait?

Можем да имплементираме trait T за тип S ако:

Traits

static dispatch

Traits

static dispatch

Traits

static dispatch

Traits

static dispatch

Traits

static dispatch

Traits

static dispatch

Trait Objects

dynamic dispatch

Има начин да се използва една версия на функцията и те да се избира at runtime.

Trait Objects

dynamic dispatch

Има начин да се използва една версия на функцията и те да се избира at runtime.
Това става с trait objects.

Trait Objects

dynamic dispatch

Ако имаме trait Stuff, &dyn Stuff да представлява какъвто и да е обект имплементиращ trait-а.

1 2 3 4 5 6 7 8 9 10 11
fn to_json(value: &dyn ToJson) -> String {
    value.to_json()
}

fn main() {
    let trait_object: &dyn ToJson = &5;

    println!("{}", to_json(trait_object));
    println!("{}", to_json(&5));
    println!("{}", to_json(&5 as &dyn ToJson));
}
5
5
5

Trait Objects

dynamic dispatch

Trait Objects

dynamic dispatch

Trait Objects

dynamic dispatch

Trait Objects

dynamic dispatch

Trait Objects

dynamic dispatch

Trait Objects

Можем да използваме trait обекти да си направим не-хомогенен вектор, който може да се принтира.

1 2 3 4 5 6 7
use std::fmt::Debug;

println!("{:?}", vec![
    &1.1 as &dyn Debug,
    &Some(String::from("Stuff")),
    &3
]);
[1.1, Some("Stuff"), 3]

Trait Objects

Големината на един trait object е два указателя - един към самата стойност и един към vtable-a.

Може да ги срещнете още като "fat pointer".

1 2
println!("{}", mem::size_of::<&u32>());
println!("{}", mem::size_of::<&dyn Debug>());
8
16

Turbofish!

Generic Traits

Нека разгледаме как бихме имплементирали Graph trait

1 2 3 4 5
trait Graph<N, E> {
    fn has_edge(&self, &N, &N) -> bool;
    fn edges(&self, &N) -> Vec<E>;
    // ...
}

Generic Traits

Ако се опитаме да направим функция

1 2 3 4
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 {
    // ...

}

Generic Traits

Ако се опитаме да направим функция

1 2 3 4
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 {
    // ...

}

Тук дефиницията на типа E за ребрата на графа няма пряко отношение към сигнатурата на функцията.

Traits

Асоциирани типове

Нека пробваме отново..

1 2 3 4 5 6 7
trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}

Traits

Асоциирани типове

Нека пробваме отново..

1 2 3 4 5 6 7
trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}

Асоциираните типове служат за, един вид, групиране на типове.

Traits

Асоциирани типове

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
struct Node;
struct Edge;
struct MyGraph;

impl Graph for MyGraph {
    type N = Node;
    type E = Edge;

    fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
        true
    }

    fn edges(&self, n: &Node) -> Vec<Edge> {
        Vec::new()
    }
}

Generic Traits

И сега ако се опитаме да направим функцията отново

1 2 3 4
fn distance<G: Graph<N=Node, E=Edge>>(graph: &G, start: &G::N, end: &G::N) -> u32 {
    // ...

}

Traits

Асоциирани типове

Може да си дефинираме trait за събиране като комбинираме Generic Traits и Associated Types:

1 2 3 4 5
trait Add<RHS=Self> {
    type Output;

    fn add(self, rhs: RHS) -> Self::Output;
}

Traits

Асоциирани типове

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
impl Add for i32 {
    type Output = i32;

    fn add(self, rhs: i32) -> i32 {
        self + rhs
    }
}

impl Add for String {
    type Output = String;

    fn add(self, rhs: String) -> String {
        format!("{} {}", self, rhs)
    }
}

Traits

Асоциирани типове

1 2 3 4 5 6 7 8 9 10 11 12
struct Student;
struct StudentGroup {
    members: Vec<Student>
}

impl Add for Student {
    type Output = StudentGroup;

    fn add(self, rhs: Student) -> StudentGroup {
        StudentGroup { members: vec![self, rhs] }
    }
}

Traits

Каква е разликата между "асоцииран тип" и "generic тип"?

Да речем, че имаме Add trait дефиниран така:

1 2 3
trait Add {
    fn add(self, rhs: Self) -> Self;
}

Това ще работи само за един и същ тип отляво, отдясно и като резултат:

1 2 3
i32.add(i32) -> i32             // Self=i32
f64.add(f64) -> f64             // Self=f64
Student.add(Student) -> Student // Self=Student

Traits

Каква е разликата между "асоцииран тип" и "generic тип"?

За да варираме дясната страна:

1 2 3
trait Add<RHS> {
    fn add(self, rhs: RHS) -> Self;
}

Това ще позволи различни типове отляво и отдясно, но резултата задължително трябва да е левия:

1 2 3
i32.add(i8) -> i32                        // Self=i32, RHS=i8
f64.add(f32) -> f64                       // Self=f64, RHS=f32
StudentGroup.add(Student) -> StudentGroup // Self=StudentGroup, RHS=Student

(Или може да върнем -> RHS вместо -> Self, за да върнем задължително десния тип.)

1
fn add(self, rhs: RHS) -> Self;

Traits

Каква е разликата между "асоцииран тип" и "generic тип"?

За да сме напълно свободни:

1 2 3
trait Add<RHS, OUTPUT> {
    fn add(self, rhs: RHS) -> OUTPUT;
}

Проблема е, че това позволява:

1 2
i32.add(i8) -> i64 // Self=i32, RHS=i8, OUTPUT=i64
i32.add(i8) -> i32 // Self=i32, RHS=i8, OUTPUT=i32

Компилатора сега няма как да знае със сигурност какъв е типа на i32.add(i8). Може да е което и да е от двете. Налага се експлицитно да го укажем, или със ::<>, или със let result: i32 = ...

Traits

Каква е разликата между "асоцииран тип" и "generic тип"?

Асоциирания тип е компромисен вариант -- можем да изберем какъв е типа на output-а, но този тип е винаги един и същ за всяка двойка ляв+десен тип:

1 2 3 4
trait Add<RHS> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}

Така можем да кажем:

1 2 3 4
impl Add<i8> for i32 {  // Имплементирай ми "добавяне на i8 към i32"
    type Output = i64;  // Като резултата ще е винаги i64
    fn add(self, rhs: i8) -> i64 { ... }
}

Заключение

Въпроси