Шаблонни типове, типажи
26 октомври 2020
Преговор
Документация
/// 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
}
/// 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
}
fn main() {}
Преговор
Атрибути
#[derive(Debug)]#[cfg(test)]#[test]
Преговор
Тестове
fn add_two(n: u32) -> u32 {
n + 2
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(add_two(2), 4);
}
}
#![allow(dead_code)]
fn main() {}
fn add_two(n: u32) -> u32 {
n + 2
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(add_two(2), 4);
}
}
Generic Types (Generics)

Generic Types (Generics)
Oбобщени типове
Generic Types (Generics)
Oбобщени типове
Вече сме ги виждали
Generic Types (Generics)
Oбобщени типове
Вече сме ги виждали
Option<T>Vec<T>
Oбобщени типове
Oбобщени типове
- Позволяват да пишем код, валиден за различни ситуации
Oбобщени типове
- Позволяват да пишем код, валиден за различни ситуации
- Поддържат само типове за разлика от C++ templates
Oбобщени типове
- Позволяват да пишем код, валиден за различни ситуации
- Поддържат само типове за разлика от C++ templates
- Мономорфизация на кода, т.е няма runtime overhead
Oбобщени типове
функции
Със знанията събрани до сега
fn identity_i32(value: i32) -> i32 {
value
}
fn identity_i8(value: u8) -> u8 {
value
}
#![allow(dead_code)]
fn main() {}
fn identity_i32(value: i32) -> i32 {
value
}
fn identity_i8(value: u8) -> u8 {
value
}
Oбобщени типове
функции
С обобщени типове
fn identity<T>(value: T) -> T {
value
}
#![allow(dead_code)]
fn main() {}
fn identity(value: T) -> T {
value
}
Oбобщени типове
структури
Нека разгледаме структурата
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 };
}
#![allow(dead_code, unused_variables)] struct Point{ x: T, y: T, } fn main() { // Може да я създадем с цели числа.. let integer = Point { x: 5, y: 10 }; // ..но може да я създадем и с числа с плаваща запетая. let float = Point { x: 1.0, y: 4.0 }; }
Oбобщени типове
структури
А какво ще стане, ако опитаме да я създадем по този начин?
fn main() {
let what_about_this = Point { x: 5, y: 4.0 }; // ??
}
Oбобщени типове
структури
А какво ще стане, ако опитаме да я създадем по този начин?
fn main() {
let what_about_this = Point { x: 5, y: 4.0 };
}
error[E0308]: mismatched types --> src/bin/main_2f97ad6350948c3ad36abcbec26aa61d24df52ce.rs:5:44 | 5 | let what_about_this = Point { x: 5, y: 4.0 }; | ^^^ expected integer, found floating-point number
#![allow(dead_code)] struct Point{ x: T, y: T } fn main() { let what_about_this = Point { x: 5, y: 4.0 }; }
Oбобщени типове
структури
Ако искаме да позволим двете координати да са различни типове
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 };
}
#![allow(dead_code, unused_variables)] struct Point{ 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бобщени типове
енумерации
Енумерациите с обобщени типове имат подобен вид:
enum Message<T, A> {
Text(T),
Action(A),
}
#![allow(dead_code)]
fn main() {}
enum Message {
Text(T),
Action(A),
}
Oбобщени типове
енумерации
Ето как се дефинира Option:
enum Option<T> {
Some(T),
None,
}
#![allow(dead_code)]
fn main() {}
enum Option {
Some(T),
None,
}
Oбобщени типове
методи
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()); // ??
}
#![allow(dead_code)] struct Point{ x: T, y: T } // Забележете impl impl Point { 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бобщени типове
методи
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
#![allow(dead_code)] struct Point{ x: T, y: T } // Забележете impl impl Point { 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бобщени типове
специализирани имплементации
В този пример само Point<f32> ще притежава този метод
// Този път няма impl<T>
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
#![allow(dead_code)] struct Point{ x: T, y: T } fn main() {} // Този път няма impl impl Point { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
Oбобщени типове
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
// Създава нова структура с `x` от `self` и `y` от `other`.
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
#![allow(dead_code)] struct Point{ x: T, y: U, } impl Point { // Създава нова структура с `x` от `self` и `y` от `other`. fn mixup (self, other: Point ) -> Point { 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); }
Упражнение
The JSON encoder
- Искаме да си направим логика, която да преобразува Rust данни в JSON.
- Да речем низа
"stuff"трябва да се преобразува до"\"stuff\"". - Числото
5трябва да се преобразува в"5". - Ако си дефинираме функция, тя ще изглежда така:
fn to_json<T>(val: T) -> String {
...
}
Упражнение
The JSON encoder
Тук възникват няколко въпроса:
Упражнение
The JSON encoder
Тук възникват няколко въпроса:
- Как да я имплементираме?
- Какъв тип да е T? Може да е всичко? Ще правиме проверки дали получаваме число или низ?
- А ако е наш собствен тип?
Типажи
Traits
- Споделенo поведение.
- Подобни на интерфейси от други езици.
Типажи
Traits
Всъщност ние сме виждали вече trait-ове.
Типажи
Traits
Всъщност ние сме виждали вече trait-ове.
Помните ли {:?}, когато отпечатваме на екрана?
Типажи
Traits
Всъщност ние сме виждали вече trait-ове.
Помните ли {:?}, когато отпечатваме на екрана?
Това има значение за данни от тип, който имплементира trait-а Debug.
Типажи
Traits
Всъщност ние сме виждали вече trait-ове.
Помните ли {:?}, когато отпечатваме на екрана?
Това има значение за данни от тип, който имплементира trait-а Debug.
Но нека се върнем на нашия пример с JSON encoder-a.
Упражнение
The JSON encoder
Дефинираме си trait:
trait ToJson {
fn to_json(&self) -> String;
}
#![allow(dead_code)]
fn main() {}
trait ToJson {
fn to_json(&self) -> String;
}
Упражнение
The JSON encoder
Сега можем да го имплементираме за някои вградени типове данни:
impl ToJson for String {
fn to_json(&self) -> String {
format!("\"{}\"", self)
}
}
#![allow(dead_code)]
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
impl ToJson for String {
fn to_json(&self) -> String {
format!("\"{}\"", self)
}
}
Упражнение
The JSON encoder
Сега можем да го имплементираме за някои вградени типове данни:
impl ToJson for i32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
impl ToJson for f32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
#![allow(dead_code)]
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
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
println!("String as json: {}", String::from("mamal").to_json());
println!("Number as json: {}", 3.to_json());
String as json: "mamal" Number as json: 3
trait ToJson {
fn to_json(&self) -> String;
}
impl ToJson for String {
fn to_json(&self) -> String {
format!(r#""{}""#, self)
}
}
impl ToJson for i32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
fn main() {
println!("String as json: {}", String::from("mamal").to_json());
println!("Number as json: {}", 3.to_json());
}
Упражнение
The JSON encoder
Можем да имаме имплементация по подразбиране:
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
trait ToJson {
fn to_json(&self) -> String {
String::from("null")
}
}
impl ToJson for () {}
fn main() {
println!("Unit as json: {}", ().to_json());
}
Упражнение
The JSON encoder
Още малко - за Option!
impl<T> ToJson for Option<T> where T: ToJson {
fn to_json(&self) -> String {
match self {
Some(val) => val.to_json(),
None => String::from("null"),
}
}
}
#![allow(dead_code)]
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
impl ToJson for Option where T: ToJson {
fn to_json(&self) -> String {
match self {
Some(val) => val.to_json(),
None => String::from("null"),
}
}
}
Упражнение
The JSON encoder
Още малко - за Option!
impl<T> ToJson for Option<T> where T: ToJson {
fn to_json(&self) -> String {
match self {
Some(val) => val.to_json(),
None => String::from("null"),
}
}
}
#![allow(dead_code)]
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
impl ToJson for Option where T: ToJson {
fn to_json(&self) -> String {
match self {
Some(val) => val.to_json(),
None => String::from("null"),
}
}
}
Забележете, че използваме type bound T: ToJson, за да работи функцията само върху Option, който съдържа стойност имплементираща ToJson.
Упражнение
The JSON encoder
В JSON има списъци, нека да пробваме да го направим за вектор:
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 = match first {
Some(first) => first.to_json(),
None => String::new(),
};
for e in iter {
result.push_str(", ");
result.push_str(&e.to_json());
}
format!("[{}]", result)
}
}
#![allow(dead_code)]
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
impl ToJson for Option where T: ToJson {
fn to_json(&self) -> String {
match self {
&Some(ref val) => val.to_json(),
&None => String::from("null"),
}
}
}
impl<'a, T> ToJson for &'a T where T: ToJson {
fn to_json(&self) -> String {
(*self).to_json()
}
}
impl ToJson for Vec where T: ToJson {
fn to_json(&self) -> String {
let mut iter = self.iter();
let first = iter.next();
let mut result = match first {
Some(first) => first.to_json(),
None => String::new(),
};
for e in iter {
result.push_str(", ");
result.push_str(&e.to_json());
}
format!("[{}]", result)
}
}
Упражнение
The JSON encoder
В JSON има списъци, нека да пробваме да го направим за вектор:
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]
trait ToJson { fn to_json(&self) -> String; }
impl ToJson for f32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
impl ToJson for Option where T: ToJson {
fn to_json(&self) -> String {
match self {
&Some(ref val) => val.to_json(),
&None => String::from("null"),
}
}
}
impl<'a, T> ToJson for &'a T where T: ToJson {
fn to_json(&self) -> String {
(*self).to_json()
}
}
impl ToJson for Vec where T: ToJson {
fn to_json(&self) -> String {
let mut iter = self.iter();
let first = iter.next();
let mut result = match first {
Some(first) => first.to_json(),
None => String::new(),
};
for e in iter {
result.push_str(", ");
result.push_str(&e.to_json());
}
format!("[{}]", result)
}
}
fn main() {
let arr = vec![Some(1.1), Some(2.2), None].to_json();
println!("Vector as json: {}", arr);
}
Упражнение
The JSON encoder
А сега и за наш си тип:
struct Student {
age: i32,
full_name: String,
number: i32,
hobby: Option<String>
}
#![allow(dead_code)]
fn main() {}
struct Student {
age: i32,
full_name: String,
number: i32,
hobby: Option
}
Упражнение
The JSON encoder
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()
)
}
}
#![allow(dead_code)]
trait ToJson { fn to_json(&self) -> String; }
struct Student {
age: i32,
full_name: String,
number: i32,
hobby: Option
}
impl ToJson for i32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
impl ToJson for Option where T: ToJson {
fn to_json(&self) -> String {
match self {
&Some(ref val) => val.to_json(),
&None => String::from("null"),
}
}
}
impl ToJson for String {
fn to_json(&self) -> String {
format!("\"{}\"", self)
}
}
fn main() {}
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
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" }
trait ToJson { fn to_json(&self) -> String; }
struct Student {
age: i32,
full_name: String,
number: i32,
hobby: Option
}
impl ToJson for i32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
impl ToJson for Option where T: ToJson {
fn to_json(&self) -> String {
match self {
&Some(ref val) => val.to_json(),
&None => String::from("null"),
}
}
}
impl ToJson for String {
fn to_json(&self) -> String {
format!("\"{}\"", self)
}
}
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()
)
}
}
fn main() {
let student = Student {
age: 16,
full_name: "Jane Doe".to_owned(),
number: 5,
hobby: Some("Tennis".to_string())
};
println!("{}", student.to_json());
}
Упражнение
The JSON encoder
Сега можем да си дефинираме функцията, от която започна всичко:
fn to_json<T: ToJson>(value: T) -> String {
value.to_json()
}
#![allow(dead_code)]
trait ToJson { fn to_json(&self) -> String; }
fn main() {}
fn to_json(value: T) -> String {
value.to_json()
}
Типажи
Traits
А ако искаме дадена стойност да имплементира повече от един trait?
fn log_json_transformation<T: ToJson + Debug>(value: T) {
println!("{:?} -> {}", value, value.to_json());
}
#![allow(dead_code)]
use std::fmt::Debug;
trait ToJson { fn to_json(&self) -> String; }
fn main() {}
fn log_json_transformation(value: T) {
println!("{:?} -> {}", value, value.to_json());
}
Traits
Кога можем да имлементираме trait?
- За определена структура може да има само една имплементация на определен trait
- За да няма грешки поради имплементации в други библиотеки, има създадени правила
Traits
Кога можем да имлементираме trait?
- За определена структура може да има само една имплементация на определен trait
- За да няма грешки поради имплементации в други библиотеки, има създадени правила
Можем да имплементираме trait T за тип S ако:
- trait-a
Tе дефиниран в нашия crate, или - типа
Sе дефиниран в нашия crate
Traits
static dispatch
Traits
static dispatch
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементация
Traits
static dispatch
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементация - При компилиране се избира правилният вариант на функцията за дадения случай
Traits
static dispatch
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементация - При компилиране се избира правилният вариант на функцията за дадения случай
- Това изпълва executable-а с тези дефиниции
Traits
static dispatch
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементация - При компилиране се избира правилният вариант на функцията за дадения случай
- Това изпълва executable-а с тези дефиниции
- Този вид генериране на код се нарича мономорфизация
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-а.
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 ToJson { fn to_json(&self) -> String; }
impl ToJson for i32 {
fn to_json(&self) -> String {
format!("{}", self)
}
}
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));
}
Trait Objects
dynamic dispatch
Trait Objects
dynamic dispatch
- Сега не изпълваме binary-то с много дефиниции
Trait Objects
dynamic dispatch
- Сега не изпълваме binary-то с много дефиниции
- Runtime нещата са малко по-сложни
Trait Objects
dynamic dispatch
- Сега не изпълваме binary-то с много дефиниции
- Runtime нещата са малко по-сложни
- Имплементирано е с vtable създаден при компилация
Trait Objects
dynamic dispatch
- Сега не изпълваме binary-то с много дефиниции
- Runtime нещата са малко по-сложни
- Имплементирано е с vtable създаден при компилация
- Не от всеки trait може да се направи trait обект
- Не трябва да връща Self като тип
- Не трябва да съдържа generic параметри
Trait Objects
Можем да използваме trait обекти да си направим не-хомогенен вектор, който може да се принтира.
use std::fmt::Debug;
println!("{:?}", vec![
&1.1 as &dyn Debug,
&Some(String::from("Stuff")),
&3
]);
[1.1, Some("Stuff"), 3]
fn main() {
use std::fmt::Debug;
println!("{:?}", vec![
&1.1 as &dyn Debug,
&Some(String::from("Stuff")),
&3
]);
}
Trait Objects
Големината на един trait object е два указателя - един към самата стойност и един към vtable-a.
Може да ги срещнете още като "fat pointer".
println!("{}", mem::size_of::<&u32>());
println!("{}", mem::size_of::<&dyn Debug>());
8 16
use std::fmt::Debug;
use std::mem;
fn main() {
println!("{}", mem::size_of::<&u32>());
println!("{}", mem::size_of::<&dyn Debug>());
}
Turbofish!

Generic Traits
Нека разгледаме как бихме имплементирали Graph trait
trait Graph<N, E> {
fn has_edge(&self, &N, &N) -> bool;
fn edges(&self, &N) -> Vec<E>;
// ...
}
#![allow(dead_code)]
fn main () {}
trait Graph {
fn has_edge(&self, &N, &N) -> bool;
fn edges(&self, &N) -> Vec;
// ...
}
Generic Traits
Ако се опитаме да направим функция
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 {
// ...
}
#![allow(dead_code, unused_variables)]
fn main () {}
trait Graph {
fn has_edge(&self, &N, &N) -> bool;
fn edges(&self, &N) -> Vec;
}
fn distance>(graph: &G, start: &N, end: &N) -> u32 {
// ...
0
}
Generic Traits
Ако се опитаме да направим функция
fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 {
// ...
}
#![allow(dead_code, unused_variables)]
fn main () {}
trait Graph {
fn has_edge(&self, &N, &N) -> bool;
fn edges(&self, &N) -> Vec;
}
fn distance>(graph: &G, start: &N, end: &N) -> u32 {
// ...
0
}
Тук дефиницията на типа E за ребрата на графа няма пряко отношение към сигнатурата на функцията.
Traits
Асоциирани типове
Нека пробваме отново..
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec<Self::E>;
}
#![allow(dead_code)]
fn main() {}
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec;
}
Traits
Асоциирани типове
Нека пробваме отново..
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec<Self::E>;
}
#![allow(dead_code)]
fn main() {}
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec;
}
Асоциираните типове служат за, един вид, групиране на типове.
Traits
Асоциирани типове
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()
}
}
#![allow(dead_code, unused_variables)]
fn main() {}
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec;
}
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 {
Vec::new()
}
}
Generic Traits
И сега ако се опитаме да направим функцията отново
fn distance<G: Graph<N=Node, E=Edge>>(graph: &G, start: &G::N, end: &G::N) -> u32 {
// ...
}
#![allow(dead_code, unused_variables)]
fn main() {}
trait Graph {
type N;
type E;
fn has_edge(&self, &Self::N, &Self::N) -> bool;
fn edges(&self, &Self::N) -> Vec;
}
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 {
Vec::new()
}
}
fn distance>(graph: &G, start: &G::N, end: &G::N) -> u32 {
// ...
0
}
Traits
Асоциирани типове
Може да си дефинираме trait за събиране като комбинираме Generic Traits и Associated Types:
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
#![allow(dead_code)]
fn main() {}
trait Add {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
Traits
Асоциирани типове
Може да си дефинираме trait за събиране като комбинираме Generic Traits и Associated Types:
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
#![allow(dead_code)]
fn main() {}
trait Add {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
RHS=Self указва тип по подразбиране. Това е позволено само за struct, enum, type и trait.
Traits
Асоциирани типове
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)
}
}
#![allow(dead_code)]
fn main() {}
trait Add {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
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
Асоциирани типове
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] }
}
}
#![allow(dead_code)]
fn main() {}
trait Add {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
struct Student;
struct StudentGroup {
members: Vec
}
impl Add for Student {
type Output = StudentGroup;
fn add(self, rhs: Student) -> StudentGroup {
StudentGroup { members: vec![self, rhs] }
}
}
Traits
Каква е разликата между "асоцииран тип" и "generic тип"?
Да речем, че имаме Add trait дефиниран така:
trait Add {
fn add(self, rhs: Self) -> Self;
}
#![allow(dead_code)]
fn main() {}
trait Add {
fn add(self, rhs: Self) -> Self;
}
Това ще работи само за един и същ тип отляво, отдясно и като резултат:
i32.add(i32) -> i32 // Self=i32
f64.add(f64) -> f64 // Self=f64
Student.add(Student) -> Student // Self=Student
Traits
Каква е разликата между "асоцииран тип" и "generic тип"?
За да варираме дясната страна:
trait Add<RHS> {
fn add(self, rhs: RHS) -> Self;
}
#![allow(dead_code)]
fn main() {}
trait Add {
fn add(self, rhs: RHS) -> Self;
}
Това ще позволи различни типове отляво и отдясно, но резултата задължително трябва да е левия:
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, за да върнем задължително десния тип.)
fn add(self, rhs: RHS) -> RHS;
Traits
Каква е разликата между "асоцииран тип" и "generic тип"?
За да сме напълно свободни:
trait Add<RHS, OUTPUT> {
fn add(self, rhs: RHS) -> OUTPUT;
}
#![allow(dead_code)]
fn main() {}
trait Add {
fn add(self, rhs: RHS) -> OUTPUT;
}
Проблема е, че това позволява:
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-а, но този тип е винаги един и същ за всяка двойка ляв+десен тип:
trait Add<RHS> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
Така можем да кажем:
impl Add<i8> for i32 { // Имплементирай ми "добавяне на i8 към i32"
type Output = i64; // Като резултата ще е винаги i64
fn add(self, rhs: i8) -> i64 { ... }
}
Заключение
- Generic Traits се използват когато искаме да имаме множество имплементации с различен тип върху една и съща структура.
- Associated Type се използва когато искаме да има точно един тип на имплементацията.