Шаблонни типове, типажи
26 октомври 2021
Административни неща
- Първото домашното приключи
Административни неща
- Първото домашното приключи
- Rust 2021
Преговор
Документация
/// 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() {}
Преговор
Тестове
#[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бобщени типове
- Позволяват да пишем код, валиден за различни ситуации
- Мономорфизация на кода, т.е няма 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 For more information about this error, try `rustc --explain E0308`. error: could not compile `rust` due to previous error
#![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); }
Константни обобщени типове
fn f<T, const N: usize>(_a: [T; N]) { }
fn main() {
f::<String, 1>([
String::from("hello"),
]);
}
fn f(_a: [T; N]) { } fn main() { f:: ([ String::from("hello"), ]); }
Константни обобщени типове
fn f<T, const N: usize>(_a: [T; N]) { }
fn main() {
f::<String, 1>([
String::from("hello"),
String::from("world"),
]);
}
error[E0308]: mismatched types --> src/bin/main_a4a8661274aab5b10c01aab9e6bf4235b8e7288b.rs:4:20 | 4 | f::<String, 1>([ | ____________________^ 5 | | String::from("hello"), 6 | | String::from("world"), 7 | | ]); | |_____^ expected an array with a fixed size of 1 element, found one with 2 elements | = note: expected array `[String; 1]` found array `[String; 2]` For more information about this error, try `rustc --explain E0308`. error: could not compile `rust` due to previous error
fn f(_a: [T; N]) { } fn main() { f:: ([ String::from("hello"), String::from("world"), ]); }
Константно фибоначи
🤔
Константно фибоначи
🤔
const fn fib(n: u32) -> u32 {
const fn rec(i: u32, a: u32, b: u32) -> u32 {
if i == 0 { a } else { rec(i - 1, b, a + b) }
}
rec(n, 0, 1)
}
fn main() {
println!("{}", fib(3));
println!("{}", fib(4));
println!("{}", fib(5));
}
2 3 5
const fn fib(n: u32) -> u32 {
const fn rec(i: u32, a: u32, b: u32) -> u32 {
if i == 0 { a } else { rec(i - 1, b, a + b) }
}
rec(n, 0, 1)
}
fn main() {
println!("{}", fib(3));
println!("{}", fib(4));
println!("{}", fib(5));
}
Константи и константни функции
const A: usize = 1;
const fn identity<T>(value: T) -> T { value }
const A: usize = 1; const fn identity(value: T) -> T { value } fn main() {}
- Константите се inline-ват по време на компилация
- Функциите се изпълняват по време на компилация
Константи и константни функции
const A: usize = 1;
const fn identity<T>(value: T) -> T { value }
const A: usize = 1; const fn identity(value: T) -> T { value } fn main() {}
- Константите се inline-ват по време на компилация
- Функциите се изпълняват по време на компилация
Имат множество правила, които няма да се съберат на един слайд, затова:
Упражнение
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
Има правила при създаването на trait object, които дефинират, че един trait е object safe:
Trait Objects
dynamic dispatch
Има правила при създаването на trait object, които дефинират, че един trait е object safe:
- Всички supertraits трябва да са object safe
Sizedне трябва да е supertrait. Т.е. не е позволеноSelf: Sized.- Не трябва да има асоциирани константи.
- Всички функции трябва да могат да се викат през trait object-a или да са маркирани експлицитно, че не са такива:
- Функции, които можем да викаме през trait object:
- Не могат да имат generic параметри, освен lifetime параметри
- Не могат да ползват Self освен за типа на receiver параметъра (self)
- Типът на receiver-a може да бъде:
&Self(i.e.&self)&mut Self(i.e&mut self)Box<Self>Rc<Self>Arc<Self>Pin<P>къдетоPе един от горните типове
- Няма
where Self: Sized.
- Експлицитните функции, които не могат да се викат през trait object:
- Имат
where Self: Sized.
- Имат
- Функции, които можем да викаме през trait object:
Trait Objects
Пример за object safe trait
trait Dispatchable {
fn by_ref(self: &Self) {}
fn by_ref_mut(self: &mut Self) {}
fn by_box(self: Box<Self>) {}
fn by_rc(self: Rc<Self>) {}
fn by_arc(self: Arc<Self>) {}
fn by_pin(self: Pin<&Self>) {}
fn with_lifetime<'a>(self: &'a Self) {}
fn nested_pin(self: Pin<Arc<Self>>) {}
}
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
trait Dispatchable {
fn by_ref(self: &Self) {}
fn by_ref_mut(self: &mut Self) {}
fn by_box(self: Box) {}
fn by_rc(self: Rc) {}
fn by_arc(self: Arc) {}
fn by_pin(self: Pin<&Self>) {}
fn with_lifetime<'a>(self: &'a Self) {}
fn nested_pin(self: Pin>) {}
}
fn main() {}
Trait Objects
Пример за object safe trait, където не можем да викаме изредените функции от trait object-a
trait NonDispatchable {
// Статичните функции не могат да се викат през trait object.
fn foo() where Self: Sized {}
// Self типа не се знае докато не се изпълни програмата.
fn returns(&self) -> Self where Self: Sized;
// `other` може да има друг конкретен тип (concrete type) спрямо receiver-a self.
fn param(&self, other: Self) where Self: Sized {}
// Обобщените типове не са съвместими с vtable.
fn typed<T>(&self, x: T) where Self: Sized {}
}
trait NonDispatchable {
// Статичните функции не могат да се викат през trait object.
fn foo() where Self: Sized {}
// Self типа не се знае докато не се изпълни програмата.
fn returns(&self) -> Self where Self: Sized;
// `other` може да има друг конкретен тип (concrete type) спрямо receiver-a self.
fn param(&self, other: Self) where Self: Sized {}
// Обобщените типове не са съвместими с vtable.
fn typed(&self, x: T) where Self: Sized {}
}
fn main() {}
Trait Objects
Пример за non-object safe traits
trait NotObjectSafe {
// ERROR: не може да има асоциирани константи
const CONST: i32 = 1;
// ERROR: асоциирана функция, която няма Self: Sized
fn foo() {}
// ERROR: Self в return
fn returns(&self) -> Self;
// ERROR: има обобщен тип
fn typed<T>(&self, x: T) {}
// ERROR: вложени receiver-и не се поддържат все още
fn nested(self: Rc<Box<Self>>) {}
}
// Self: Sized трейтовете не са object safe.
trait TraitWithSize where Self: Sized {}
use std::rc::Rc;
trait NotObjectSafe {
// ERROR: не може да има асоциирани константи
const CONST: i32 = 1;
// ERROR: асоциирана функция, която няма Self: Sized
fn foo() {}
// ERROR: Self в return
fn returns(&self) -> Self;
// ERROR: има обобщен тип
fn typed(&self, x: T) {}
// ERROR: вложени receiver-и не се поддържат все още
fn nested(self: Rc>) {}
}
// Self: Sized трейтовете не са object safe.
trait TraitWithSize where Self: Sized {}
fn main() {}
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, node1: &N, node2: &N) -> bool;
fn edges(&self, node: &N) -> Vec<E>;
// ...
}
#![allow(dead_code)]
fn main () {}
trait Graph {
fn has_edge(&self, node1: &N, node2: &N) -> bool;
fn edges(&self, node: &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, node1: &N, node2: &N) -> bool;
fn edges(&self, node: &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, node1: &N, node2: &N) -> bool;
fn edges(&self, node: &N) -> Vec;
}
fn distance>(graph: &G, start: &N, end: &N) -> u32 {
// ...
0
}
Тук дефиницията на типа E за ребрата на графа няма пряко отношение към сигнатурата на функцията.
Traits
Асоциирани типове
Нека пробваме отново..
trait Graph {
type N;
type E;
fn has_edge(&self, node1: &Self::N, node2: &Self::N) -> bool;
fn edges(&self, node: &Self::N) -> Vec<Self::E>;
}
#![allow(dead_code)]
fn main() {}
trait Graph {
type N;
type E;
fn has_edge(&self, node1: &Self::N, node2: &Self::N) -> bool;
fn edges(&self, node: &Self::N) -> Vec;
}
Traits
Асоциирани типове
Нека пробваме отново..
trait Graph {
type N;
type E;
fn has_edge(&self, node1: &Self::N, node2: &Self::N) -> bool;
fn edges(&self, node: &Self::N) -> Vec<Self::E>;
}
#![allow(dead_code)]
fn main() {}
trait Graph {
type N;
type E;
fn has_edge(&self, node1: &Self::N, node2: &Self::N) -> bool;
fn edges(&self, node: &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, node1: &Self::N, node2: &Self::N) -> bool;
fn edges(&self, node: &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, node1: &Self::N, node2: &Self::N) -> bool;
fn edges(&self, node: &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 се използват когато искаме да има имаме свобода на типа, но да бъде фиксиран при имплементация.