Шаблонни типове, типажи
22 октомври 2024
Административни неща
- Първото домашното е пуснато
- https://fmi.rust-lang.bg/announcements/2
- Срок до четвъртък, 31 октомври, 18:00
Преговор
- utf8 низове
- символи (
char) и байтове (u8) - итератори
- итерация по символи (
string.chars()) и по байтове (string.bytes())
Generic Types (Generics)
Generic Types (Generics)
Шаблонни типове
Вече сме ги виждали
Option<T>Vec<T>- …
Generics
функции
Позволяват да пишем код, валиден за различни ситуации
- със знанията събрани до сега:
fn identity_i32(value: i32) -> i32 {
value
}
fn identity_i8(value: u8) -> u8 {
value
}
fn main() {}
fn identity_i32(value: i32) -> i32 {
value
}
fn identity_i8(value: u8) -> u8 {
value
}
Generics
функции
Позволяват да пишем код, валиден за различни ситуации
- с generic типове:
fn identity<T>(value: T) -> T {
value
}
fn main() {}
fn identity(value: T) -> T {
value
}
Generics
функции
Можем да пишем по-сложни функции..
fn sum<T>(a: T, b: T) -> T {
a + b
}
Generics
функции
Можем да пишем по-сложни функции.. ама всъщност не
fn sum<T>(a: T, b: T) -> T {
a + b
}
error[E0369]: cannot add `T` to `T` --> src/bin/main_ac0e659fcafa1345b52e1d46ce02b96c1d43fbfb.rs:4:7 | 4 | a + b | - ^ - T | | | T | help: consider restricting type parameter `T` | 3 | fn sum<T: std::ops::Add<Output = T>>(a: T, b: T) -> T { | +++++++++++++++++++++++++++ For more information about this error, try `rustc --explain E0369`. error: could not compile `rust` (bin "main_ac0e659fcafa1345b52e1d46ce02b96c1d43fbfb") due to 1 previous error
fn main() {}
fn sum(a: T, b: T) -> T {
a + b
}
Generics
функции
- generic функция трябва да е валидна за всички типове, с които може да се инстанцира
Generics
функции
- generic функция трябва да е валидна за всички типове, с които може да се инстанцира
sum<T>трябва да е валидна за всички възможниT
Generics
функции
- generic функция трябва да е валидна за всички типове, с които може да се инстанцира
sum<T>трябва да е валидна за всички възможниT- но не всеки тип имплементира операция събиране
Generics
функции
- generic функция трябва да е валидна за всички типове, с които може да се инстанцира
sum<T>трябва да е валидна за всички възможниT- но не всеки тип имплементира операция събиране
- за целта трябва да ограничим
Tсамо до типове, които имплементират събиране
Generics
функции
- generic функция трябва да е валидна за всички типове, с които може да се инстанцира
sum<T>трябва да е валидна за всички възможниT- но не всеки тип имплементира операция събиране
- за целта трябва да ограничим
Tсамо до типове, които имплементират събиране - но това малко по-късно
Generics
структури
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 }; }
Generics
структури
Ако искаме да позволим двете координати да са различни типове
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_string = 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_string = Point { x: 5, y: "4.0" }; }
Generics
енумерации
enum Message<T, A> {
Text(T),
Action(A),
}
enum Option<T> {
Some(T),
None,
}
fn main() {}
enum Message {
Text(T),
Action(A),
}
enum Option {
Some(T),
None,
}
Generics
методи
struct Point<T> { x: T, y: T }
// Забележете impl<T>
impl<T> Point<T> {
fn coords(&self) -> (&T, &T) {
(&self.x, &self.y)
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("coords = {:?}", p.coords());
}
coords = (5, 10)
struct Point{ x: T, y: T } // Забележете impl impl Point { fn coords(&self) -> (&T, &T) { (&self.x, &self.y) } } fn main() { let p = Point { x: 5, y: 10 }; println!("coords = {:?}", p.coords()); }
Generics
методи
impl<T> Point<T> { ... }
означава
за всяко Т: impl Point<T> { ... }
Generics
специализирани имплементации
- не е задължително да имплементираме методите за всички типове
T - в този пример само
Point<f32>ще притежава метода
struct Point<T> { x: T, y: T }
// Този път няма impl<T>
impl Point<f32> {
fn dist_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
println!("dist = {:?}", Point { x: 5.0, y: 10.0 }.dist_from_origin());
// println!("dist = {:?}", Point { x: 5, y: 10 }.dist_from_origin());
}
struct Point{ x: T, y: T } // Този път няма impl impl Point { fn dist_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } fn main() { println!("dist = {:?}", Point { x: 5.0, y: 10.0 }.dist_from_origin()); // println!("dist = {:?}", Point { x: 5, y: 10 }.dist_from_origin()); }
Generics
специализирани имплементации
struct Point<T, U> { x: T, y: U }
// за всяко Т: impl Point<T, T>
impl<T> Point<T, T> {
fn coords_1(&self) -> (&T, &T) {
(&self.x, &self.y)
}
}
// за всяко T, за всяко U: impl Point<T, U>
impl<T, U> Point<T, U> {
fn coords_2(&self) -> (&T, &U) {
(&self.x, &self.y)
}
}
struct Point{ x: T, y: U } // за всяко Т: impl Point impl Point { fn coords_1(&self) -> (&T, &T) { (&self.x, &self.y) } } // за всяко T, за всяко U: impl Point impl Point { fn coords_2(&self) -> (&T, &U) { (&self.x, &self.y) } } fn main() {}
Generics
шаблонни методи
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'
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"), ]); }
Упражнение
The JSON encoder
- Искаме да си направим логика, която да преобразува Rust данни в JSON
- Числото
5трябва да се преобразува в"5" - Низа
"stuff"трябва да се преобразува до"\"stuff\"" - Няма да разглеждаме специални частни случаи като escape codes
- Ако си дефинираме функция, тя ще изглежда така:
fn to_json<T>(val: T) -> String {
...
}
Упражнение
The JSON encoder
Тук възникват няколко въпроса:
Упражнение
The JSON encoder
Тук възникват няколко въпроса:
- Как да я имплементираме?
- Какъв тип да е
T? Може да е всичко? Ще правиме проверки дали получаваме число или низ? - А ако е наш собствен тип?
Типажи
Traits
- Споделенo поведение.
- Подобни на интерфейси от други езици.
Типажи
Traits
- виждали сме trait-ове.
Типажи
Traits
- виждали сме trait-ове.
- placeholder-a
{:?}вprintln!използва traitDebug
Типажи
Traits
- виждали сме trait-ове.
- placeholder-a
{:?}вprintln!използва traitDebug - но нека се върнем на нашия пример с JSON encoder-a.
Упражнение
The JSON encoder
Дефинираме си trait:
trait ToJson {
fn to_json(&self) -> String;
}
fn main() {}
trait ToJson {
fn to_json(&self) -> String;
}
Упражнение
The JSON encoder
Сега можем да го имплементираме за някои вградени типове данни:
impl ToJson for String {
fn to_json(&self) -> String {
format!("\"{}\"", self)
}
}
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)
}
}
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("mama").to_json());
println!("Number as json: {}", 3.to_json());
String as json: "mama" 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("mama").to_json());
println!("Number as json: {}", 3.to_json());
}
Отклонение
макрото format!
print!иprintln!печатат на стандартния изходformat!връщаString- поддържат един и същ синтаксис и placeholders
Упражнение
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());
}
Упражнение
Ограничения (type bounds)
Още малко - за 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"),
}
}
}
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 bounds)
Още малко - за 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"),
}
}
}
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.
Упражнение
Ограничения (type bounds)
impl<T: ToJson> ToJson for Option<T> {
}
е еквиваленто на
impl<T> ToJson for Option<T>
where
T: 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 mut result = match iter.next() {
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() {}
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 mut result = match iter.next() {
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>
}
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()
)
}
}
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()
}
trait ToJson { fn to_json(&self) -> String; }
fn main() {}
fn to_json(value: T) -> String {
value.to_json()
}
Traits
множество типажи
А ако искаме даден параметър да имплементира повече от един типаж?
fn log_json_transformation<T>(value: T)
where
T: ToJson + Debug,
{
println!("{:?} -> {}", value, value.to_json());
}
use std::fmt::Debug;
trait ToJson { fn to_json(&self) -> String; }
fn main() {}
fn log_json_transformation(value: T)
where
T: ToJson + Debug,
{
println!("{:?} -> {}", value, value.to_json());
}
Traits
множество типажи
fn log_json_transformation<T>(value: T)
where
T: ToJson + Debug,
{
}
use std::fmt::Debug;
trait ToJson { fn to_json(&self) -> String; }
fn main() {}
fn log_json_transformation(value: T)
where
T: ToJson + Debug,
{
}
е еквивалентно на
fn log_json_transformation<T>(value: T)
where
T: ToJson,
T: Debug,
{
}
use std::fmt::Debug;
trait ToJson { fn to_json(&self) -> String; }
fn main() {}
fn log_json_transformation(value: T)
where
T: ToJson,
T: Debug,
{
}
Traits
Кога можем да имлементираме типаж?
- За определена структура може да има само една имплементация на определен trait
- За да няма грешки поради имплементации в други библиотеки, има създадени правила
Traits
Кога можем да имлементираме типаж?
- За определена структура може да има само една имплементация на определен trait
- За да няма грешки поради имплементации в други библиотеки, има създадени правила
Orphan rule: можем да имплементираме trait T за тип S ако:
- trait-a
Tе дефиниран в нашия crate, или - типа
Sе дефиниран в нашия crate
Traits
static dispatch
Traits
static dispatch
fn to_json<T: ToJson>(value: T) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: T) -> String {
value.to_json()
}
Traits
static dispatch
fn to_json<T: ToJson>(value: T) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: T) -> String {
value.to_json()
}
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементация
Traits
static dispatch
fn to_json<T: ToJson>(value: T) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: T) -> String {
value.to_json()
}
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементацияto_json::<String>to_json::<i32>to_json::<Student>- …
Traits
static dispatch
fn to_json<T: ToJson>(value: T) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: T) -> String {
value.to_json()
}
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементацияto_json::<String>to_json::<i32>to_json::<Student>- …
- При компилиране се избира правилният вариант на функцията за дадения случай
Traits
static dispatch
fn to_json<T: ToJson>(value: T) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: T) -> String {
value.to_json()
}
- Компилатора генерира отделна версия на
to_jsonза всяка нейна имплементацияto_json::<String>to_json::<i32>to_json::<Student>- …
- При компилиране се избира правилният вариант на функцията за дадения случай
- Това се нарича мономорфизация
Turbofish!

Trait Objects
dynamic dispatch
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
Trait Objects
dynamic dispatch
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
- една версия на функцията -
to_jsonвече не е generic
Trait Objects
dynamic dispatch
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
- една версия на функцията -
to_jsonвече не е generic - подава се
&dyn ToJson- нещо, което имплементира traitToJson
Trait Objects
dynamic dispatch
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
fn main() {}
trait ToJson { fn to_json(&self) -> String; }
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
- една версия на функцията -
to_jsonвече не е generic - подава се
&dyn ToJson- нещо, което имплементира traitToJson - коя точно имплементация се използва се решава по време на изпълнение
Trait Objects
dynamic dispatch
&dyn Traitсе нарича trait object
Trait Objects
dynamic dispatch
&dyn Traitсе нарича trait object- дебел указател (fat pointer), съдържа:
Trait Objects
dynamic dispatch
&dyn Traitсе нарича trait object- дебел указател (fat pointer), съдържа:
- указател към обект
- указател към виртуална таблица за съответния trait
Trait Objects
dynamic dispatch
&dyn Traitсе нарича trait object- дебел указател (fat pointer), съдържа:
- указател към обект
- указател към виртуална таблица за съответния trait
- това още се нарича type erasure
Trait Objects
dynamic dispatch
&dyn Traitсе нарича trait object- дебел указател (fat pointer), съдържа:
- указател към обект
- указател към виртуална таблица за съответния trait
- това още се нарича type erasure
- забравяме информацията какъв е конкретния тип на обекта
- помним само какви интерфейси имплементира
Trait Objects
dynamic dispatch
&dyn Traitсе нарича trait object- дебел указател (fat pointer), съдържа:
- указател към обект
- указател към виртуална таблица за съответния trait
- това още се нарича type erasure
- забравяме информацията какъв е конкретния тип на обекта
- помним само какви интерфейси имплементира
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>());
}
Trait Objects
dynamic dispatch
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
Можем да използваме trait objects да си направим не-хомогенен вектор.
impl ToJson for Box<dyn ToJson> {
fn to_json(&self) -> String {
(**self).to_json()
}
}
fn main() {
let values = vec![
Box::new(1.1_f32) as Box<dyn ToJson>,
Box::new(3_i32),
Box::new(String::from("Stuff")),
];
println!("{}", to_json(&values));
}
[1.1, 3, "Stuff"]
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) } }
impl ToJson for String { fn to_json(&self) -> String { format!("{:?}", self) } }
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 to_json(value: &dyn ToJson) -> String { value.to_json() }
impl ToJson for Box {
fn to_json(&self) -> String {
(**self).to_json()
}
}
fn main() {
let values = vec![
Box::new(1.1_f32) as Box,
Box::new(3_i32),
Box::new(String::from("Stuff")),
];
println!("{}", to_json(&values));
}
Trait Objects
object safety
- не от всеки trait може да се направи trait object
Trait Objects
object safety
- не от всеки trait може да се направи trait object
- тези, от които може, се наричат object safe _(доста глупаво име)_
Trait Objects
object safety
- не от всеки trait може да се направи trait object
- тези, от които може, се наричат object safe _(доста глупаво име)_
- с две думи - един trait е object safe, ако може да се построи виртуална таблица за него
Trait Objects
object safety
Примери за неща, които правят trait-а не-object-safe
trait NotObjectSafe {
type Item;
// шаблонни функции
fn generic<T>(&self, val: &T);
// функции, които приемат аргумент от тип Self или връщат Self
fn receiver_by_value(self);
fn self_argument(&self, other: Self);
fn duplicate(&self) -> Self;
// функции, които приемат или връщат асоцииран тип
fn get_item(&self) -> &Self::Item;
fn set_item(&mut self, item: &Self::Item);
// и други
}
fn main() {}
trait NotObjectSafe {
type Item;
// шаблонни функции
fn generic(&self, val: &T);
// функции, които приемат аргумент от тип Self или връщат Self
fn receiver_by_value(self);
fn self_argument(&self, other: Self);
fn duplicate(&self) -> Self;
// функции, които приемат или връщат асоцииран тип
fn get_item(&self) -> &Self::Item;
fn set_item(&mut self, item: &Self::Item);
// и други
}
Trait Objects
Хитрина - можем да направим trait object-safe, ако сложим ограничение where Self: Sized на проблематичните функции
trait ObjectSafe {
fn to_json(&self) -> String;
fn to_bytes(self) -> Vec<u8> where Self: Sized;
}
fn main() {}
trait ObjectSafe {
fn to_json(&self) -> String;
fn to_bytes(self) -> Vec where Self: Sized;
}
Trait Objects
Хитрина - можем да направим trait object-safe, ако сложим ограничение where Self: Sized на проблематичните функции
trait ObjectSafe {
fn to_json(&self) -> String;
fn to_bytes(self) -> Vec<u8> where Self: Sized;
}
fn main() {}
trait ObjectSafe {
fn to_json(&self) -> String;
fn to_bytes(self) -> Vec where Self: Sized;
}
- няма да обяснявам много защо
Trait Objects
Хитрина - можем да направим trait object-safe, ако сложим ограничение where Self: Sized на проблематичните функции
trait ObjectSafe {
fn to_json(&self) -> String;
fn to_bin(self) -> Vec<u8> where Self: Sized;
}
fn main() {
let string = String::from("foo");
let trait_obj = &string as &dyn ObjectSafe;
println!("{}", trait_obj.to_json());
// error: the `to_bin` method cannot be invoked on a trait object
// println!("{:?}", trait_obj.to_bin());
println!("{}", string.to_json());
println!("{:x?}", string.to_bin());
}
"foo" "foo" [66, 6f, 6f]
impl ObjectSafe for String {
fn to_json(&self) -> String { format!("{:?}", self) }
fn to_bin(self) -> Vec where Self: Sized { self.as_bytes().to_owned() }
}
trait ObjectSafe {
fn to_json(&self) -> String;
fn to_bin(self) -> Vec where Self: Sized;
}
fn main() {
let string = String::from("foo");
let trait_obj = &string as &dyn ObjectSafe;
println!("{}", trait_obj.to_json());
// error: the `to_bin` method cannot be invoked on a trait object
// println!("{:?}", trait_obj.to_bin());
println!("{}", string.to_json());
println!("{:x?}", string.to_bin());
}
Асоциирани типове
Асоциирани типове
Позволяват задаване на различен тип за всяка имплементрация на trait-а
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Асоциирани типове
impl Iterator for std::str::Chars {
type Item = char;
fn next(&mut self) -> Option<Self::Item> { ... }
}
impl Iterator for std::str::Bytes {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> { ... }
}
Асоциирани типове
и шаблонни типажи (generic traits)
Може да си дефинираме trait за събиране като комбинираме Generic Traits и Associated Types:
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
fn main() {}
trait Add {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
Асоциирани типове
и шаблонни типажи (generic traits)
Може да си дефинираме trait за събиране като комбинираме Generic Traits и Associated Types:
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
fn main() {}
trait Add {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
RHS=Self указва тип по подразбиране. Това е позволено само за struct, enum, type и trait.
Асоциирани типове
и шаблонни типажи (generic 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)
}
}
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)
}
}
Асоциирани типове
и шаблонни типажи (generic 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] }
}
}
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] }
}
}
Асоциирани типове
Каква е разликата между "асоцииран тип" и "generic тип"?
Да речем, че имаме Add trait дефиниран така:
trait Add {
fn add(self, rhs: Self) -> Self;
}
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
Асоциирани типове
Каква е разликата между "асоцииран тип" и "generic тип"?
За да варираме дясната страна:
trait Add<RHS> {
fn add(self, rhs: RHS) -> Self;
}
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;
Асоциирани типове
Каква е разликата между "асоцииран тип" и "generic тип"?
За да сме напълно свободни:
trait Add<RHS, OUTPUT> {
fn add(self, rhs: RHS) -> OUTPUT;
}
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 = ...
Асоциирани типове
Каква е разликата между "асоцииран тип" и "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 се използват когато искаме да има имаме свобода на типа, но да бъде фиксиран при имплементация.