Generics and traits II
04 ноември 2025
Преговор
Преговор
- generic функции
Преговор
- generic функции
- generic структури и методи към тях
Преговор
- generic функции
- generic структури и методи към тях
- traits
Преговор
- generic функции
- generic структури и методи към тях
- traits
- trait bounds
fn convert_to_json<T: ToJson>(x: T) -> String
Преговор
- generic функции
- generic структури и методи към тях
- traits
- trait bounds
fn convert_to_json<T: ToJson>(x: T) -> String
- trait objects и dynamic dispatch
fn convert_to_json(x: &dyn ToJson)
Асоциирани типове
Продължение
Асоциирани типове
Позволяват задаване на различен тип за всяка имплементрация на 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> { ... }
}
Асоциирани типове
- типажи с асоциирани типове не могат да се предърнат в trait object
- няма как да построим
&dyn Iterator - всеки итератор може да връща различен тип
- няма как да построим
- обикновенно се работи с generics
fn first_and_last<I: Iterator>(iter: I) -> Option<???> {
let mut iter = iter;
let first = match iter.next() {
Some(elem) => elem,
None => return None,
};
let last = match iter.last() {
Some(elem) => elem,
None => return None,
};
Some((first, last))
}
Асоциирани типове
- типажи с асоциирани типове не могат да се предърнат в trait object
- няма как да построим
&dyn Iterator - всеки итератор може да връща различен тип
- няма как да построим
- обикновенно се работи с generics
fn first_and_last<I: Iterator>(iter: I) -> Option<(I::Item, I::Item)> {
let mut iter = iter;
let first = match iter.next() {
Some(elem) => elem,
None => return None,
};
let last = match iter.last() {
Some(elem) => elem,
None => return None,
};
Some((first, last))
}
fn main() {}
fn first_and_last(iter: I) -> Option<(I::Item, I::Item)> {
let mut iter = iter;
let first = match iter.next() {
Some(elem) => elem,
None => return None,
};
let last = match iter.last() {
Some(elem) => elem,
None => return None,
};
Some((first, last))
}
Асоциирани типове
- можем да добавяме допълнителни ограничения на асоциирания тип
fn print_all<I: Iterator>(iter: I) {
for elem in iter {
print!("{:?} ", elem);
}
println!();
}
error[E0277]: `<I as Iterator>::Item` doesn't implement `Debug` --> src/bin/main_a932cd0158a8f5da3a503b6d3f087743cafb2b95.rs:5:25 | 5 | print!("{:?} ", elem); | ---- ^^^^ `<I as Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `Debug` | | | required by this formatting parameter | = help: the trait `Debug` is not implemented for `<I as Iterator>::Item` = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the macro `print` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider further restricting the associated type | 3 | fn print_all<I: Iterator>(iter: I) where <I as Iterator>::Item: Debug { | ++++++++++++++++++++++++++++++++++ For more information about this error, try `rustc --explain E0277`. error: could not compile `rust` (bin "main_a932cd0158a8f5da3a503b6d3f087743cafb2b95") due to 1 previous error
fn main() {}
fn print_all(iter: I) {
for elem in iter {
print!("{:?} ", elem);
}
println!();
}
Асоциирани типове
- можем да добавяме допълнителни ограничения на асоциирания тип
fn print_all<I: Iterator>(iter: I)
where
I::Item: std::fmt::Debug
{
for elem in iter {
print!("{:?} ", elem);
}
println!();
}
fn main() {}
fn print_all(iter: I)
where
I::Item: std::fmt::Debug
{
for elem in iter {
print!("{:?} ", elem);
}
println!();
}
Асоциирани типове
- можем да окажем фиксиран асоцииран тип
fn sum<I: Iterator<Item=i32>>(iter: I) -> i32 {
let mut sum = 0;
for elem in iter {
sum += elem;
}
sum
}
fn main() {}
fn sum>(iter: I) -> i32 {
let mut sum = 0;
for elem in iter {
sum += elem;
}
sum
}
Асоциирани типове
- когато фиксираме асоциирания тип можем да построим и trait object
- защото вече всички възможни итератори имат една и съща форма
fn sum(iter: &mut dyn Iterator<Item=i32>) -> i32 {
let mut sum = 0;
for elem in iter {
sum += elem;
}
sum
}
fn main() {}
fn sum(iter: &mut dyn Iterator- ) -> i32 {
let mut sum = 0;
for elem in iter {
sum += elem;
}
sum
}
Traits
пълно име
Когато напишем Type::item, това означава:
Traits
пълно име
Когато напишем Type::item, това означава:
- ако за типа има дефиниран метод
item- това е метода
Traits
пълно име
Когато напишем Type::item, това означава:
- ако за типа има дефиниран метод
item- това е метода - ако in scope има само един trait, който предоставя функция или асоцииран тип
item- това е съответната функция/асоцииран тип от trait-а
Traits
пълно име
Когато напишем Type::item, това означава:
- ако за типа има дефиниран метод
item- това е метода - ако in scope има само един trait, който предоставя функция или асоцииран тип
item- това е съответната функция/асоцииран тип от trait-а - ако in scope има повече от един такъв trait - тогава е двусмислено
Traits
пълно име
Ако има двусмислица - трябва да се използва пълното квалифицирано име
<Type as Trait>::item
Пример
<std::str::Chars as std::iter::Iterator>::Item // char
<std::str::Chars as std::iter::Iterator>::next // fn (&mut std::str::Chars) -> Option<char>
Box
Box<T>- умен указател
Box
Box<T>- умен указател- алокира стойност в динамичната памет (на heap-а)
Box
Box<T>- умен указател- алокира стойност в динамичната памет (на heap-а)
- при деструкториране - стойността се деалокира
Box
Box<T>- умен указател- алокира стойност в динамичната памет (на heap-а)
- при деструкториране - стойността се деалокира
- (подобно на
std::unique_ptr<T>от C++
Box
struct User {
name: String,
age: i32,
}
let user = Box::new(User {
name: String::from("Тодор"),
age: 25,
});
fn main() {
struct User {
name: String,
age: i32,
}
let user = Box::new(User {
name: String::from("Тодор"),
age: 25,
});
}
Box
Box<User>
+–––––––+
stack frame │ ptr=• │
+–––––│–+
+–––+
│
│
[–│–––––––––– User –––––––––––––]
+–V–––––+–––––––+–––––––+–––––––+
heap │ name │ age │
+–––––––+–––––––+–––––––+–––––––+
Box
дереференциране
*Box<T>⟶T- това работи, защото
i32: Copy
let x = Box::new(5);
let y = Box::new(10);
// error: Cannot add `Box<{integer}>` to `Box<{integer}>`
// println!("{}", x + y);
// OK
let sum = *x + *y;
println!("{} + {} = {}", x, y, sum);
5 + 10 = 15
fn main() {
let x = Box::new(5);
let y = Box::new(10);
// error: Cannot add `Box<{integer}>` to `Box<{integer}>`
// println!("{}", x + y);
// OK
let sum = *x + *y;
println!("{} + {} = {}", x, y, sum);
}
Box
дереференциране
let boxed_user = Box::new(User {
/* ... */
});
let user = *boxed_user; // user: User
println!("{:?}", user);
// error: borrow of moved value: `boxed_user`
// println!("{:?}", boxed_user);
User { name: "Тодор", age: 25 }
fn main() {
#[derive(Debug)] struct User { name: String, age: i32 }
let boxed_user = Box::new(User {
name: String::from("Тодор"),
age: 25,
/* ... */
});
let user = *boxed_user; // user: User
println!("{:?}", user);
// error: borrow of moved value: `boxed_user`
// println!("{:?}", boxed_user);
}
Box
дереференциране
let boxed_user = Box::new(User {
/* ... */
});
let user = &*boxed_user; // user: &User
println!("{:?}", user);
// Ok
println!("{:?}", boxed_user);
User { name: "Тодор", age: 25 } User { name: "Тодор", age: 25 }
fn main() {
#[derive(Debug)] struct User { name: String, age: i32 }
let boxed_user = Box::new(User {
name: String::from("Тодор"),
age: 25,
/* ... */
});
let user = &*boxed_user; // user: &User
println!("{:?}", user);
// Ok
println!("{:?}", boxed_user);
}
Box
методи
Методи могат да се извикват през Box
impl User {
fn say_hi(&self) {
println!("{}: {}", self.name, "здравей!");
}
}
let boxed_user = Box::new(User {
/* ... */
});
boxed_user.say_hi();
Тодор: здравей!
fn main() {
#[derive(Debug)] struct User { name: String, age: i32 }
impl User {
fn say_hi(&self) {
println!("{}: {}", self.name, "здравей!");
}
}
let boxed_user = Box::new(User {
name: String::from("Тодор"),
age: 25,
/* ... */
});
boxed_user.say_hi();
}
Box
методи и функции
Референция към вътрешността може да се вземе с &*boxed или &boxed
fn greet(user: &User) {
println!("Здравей, {}!", user.name);
}
let boxed_user = Box::new(User {
/* ... */
});
greet(&*boxed_user); // OK
greet(&boxed_user); // OK, но защо?
Здравей, Тодор! Здравей, Тодор!
fn main() {
#[derive(Debug)] struct User { name: String, age: i32 }
fn greet(user: &User) {
println!("Здравей, {}!", user.name);
}
let boxed_user = Box::new(User {
name: String::from("Тодор"),
age: 25,
/* ... */
});
greet(&*boxed_user); // OK
greet(&boxed_user); // OK, но защо?
}
Deref coersion
- това поведение не е специфично за
Box
Deref coersion
- това поведение не е специфично за
Box - имплицитно конвертиране между референции -- в някои специфични случаи
&X⟶&Y
Deref coersion
- това поведение не е специфично за
Box - имплицитно конвертиране между референции -- в някои специфични случаи
&X⟶&Y- това поведение се контролира от trait
Deref - и се нарича deref coersion
Deref coersion
std::ops::Deref
// std::ops::Deref
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
- yказва, че е позволено имплицитно конвертиране от
&Selfдо&Target
Deref coersion
std::ops::Deref
// std::ops::Deref
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
- yказва, че е позволено имплицитно конвертиране от
&Selfдо&Target - компилатора прилага deref coersion при
- достъпване на методи и полета
- подаване като аргумент на функция
- присвояване на променлива
Deref coersion
std::ops::Deref
Примери
&Box<T>⟶&T&Vec<T>⟶&[T]&String⟶&str
Deref coersion
std::ops::Deref
Примери
&Box<T>⟶&T&Vec<T>⟶&[T]&String⟶&str
Deref е предвидено да се използва специално за типове, които се държат все едно са Target или &Target
Box<T>е указател къмТ+ собственостVec<T>е резен от масив + собственост + реалокацияStringе резен от низ + собственост + реалокация
Deref coersion
// std::ops::DerefMut
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Deref coersion
// std::ops::DerefMut
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Derefпозволява имплицитно&Self⟶&Target
Deref coersion
// std::ops::DerefMut
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Derefпозволява имплицитно&Self⟶&TargetDerefMutпозволява имплицитно&mut Self⟶&mut Target
Deref coersion
// std::ops::DerefMut
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Derefпозволява имплицитно&Self⟶&TargetDerefMutпозволява имплицитно&mut Self⟶&mut TargetDerefMutизисква имплементация наDeref
Deref coersion
// std::ops::DerefMut
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Derefпозволява имплицитно&Self⟶&TargetDerefMutпозволява имплицитно&mut Self⟶&mut TargetDerefMutизисква имплементация наDerefSelf::Targete<Self as Deref>::Target
Sized
Sized
Какво представлява ?Sized в дефиницията на Deref?
// std::ops::Deref
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
Sized
Sizedе ✨магически✨ trait
Sized
Sizedе ✨магически✨ trait- имплементира се автоматично за почти всички типове
Sized
Sizedе ✨магически✨ trait- имплементира се автоматично за почти всички типове
- означава, че можем да създаваме стойностти от тези типове
Sized
Sizedе ✨магически✨ trait- имплементира се автоматично за почти всички типове
- означава, че можем да създаваме стойностти от тези типове
- примерно:
u8,Vec<T>,&T
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Това са типове, които:
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Това са типове, които:
- съществуват в типовата система на езика
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Това са типове, които:
- съществуват в типовата система на езика
- но не можем да създаваме стойности от тях
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Това са типове, които:
- съществуват в типовата система на езика
- но не можем да създаваме стойности от тях
- не можем да ги използваме by value
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Това са типове, които:
- съществуват в типовата система на езика
- но не можем да създаваме стойности от тях
- не можем да ги използваме by value
- винаги се използват зад някакъв вид референция
Sized
Има типове, които не са Sized
str[u32]dyn MyTrait
Това са типове, които:
- съществуват в типовата система на езика
- но не можем да създаваме стойности от тях
- не можем да ги използваме by value
- винаги се използват зад някакъв вид референция
&str,Box<str>, …
Sized
Защо?
impl Deref for String {
type Target = ???;
fn deref(&self) -> &Self::Target { /* ... */ }
}
Какъв трабва да е типа на Target?
Sized
Защо?
impl Deref for String {
type Target = ???;
fn deref(&self) -> &Self::Target { /* ... */ }
}
Какъв трабва да е типа на Target?
- искаме
deref: fn(&String) -> &str
Sized
Защо?
impl Deref for String {
type Target = ???;
fn deref(&self) -> &Self::Target { /* ... */ }
}
Какъв трабва да е типа на Target?
- искаме
deref: fn(&String) -> &str &Target = &str
Sized
Защо?
impl Deref for String {
type Target = ???;
fn deref(&self) -> &Self::Target { /* ... */ }
}
Какъв трабва да е типа на Target?
- искаме
deref: fn(&String) -> &str &Target = &strTarget = str
Sized
?Sized
- обикновенно искаме да работим с типове, които са
Sized - би било досадно да го пишем навсякъде
- затова това ограничение се добавя автоматично на всички типови параметри
fn foo<T>(x: T) {}
// е еквиваленто на
fn foo<T: Sized>(x: T) {}
Sized
?Sized
?Sizedе синтаксис, който позволява да махнем това имплицитно ограничение- означава, че типа
Tможе да е Sized или да не е Sized
fn foo<T: ?Sized>(x: &T) {}
// error: `x` doesn't have a size known at compile-time
// fn foo<T: ?Sized>(x: T) {}
Sized
traits
- traits нямат имплицитно ограничение
Self: Sized - на теория
Selfможе да е всякакъв типа - понякога трябва да добавим ограничение сами
trait Aggregate {
type Output;
// Нещо като `&[str]` не може да съшествува.
// Затова трябва да добавим изискване `Self: Sized`.
fn aggregate(data: &[Self]) -> Self::Output
where Self: Sized;
}
fn main() {}
trait Aggregate {
type Output;
// Нещо като `&[str]` не може да съшествува.
// Затова трябва да добавим изискване `Self: Sized`.
fn aggregate(data: &[Self]) -> Self::Output
where Self: Sized;
}
Sized
traits
- ограничението може да се сложи и на друго място
trait Aggregate
where
Self: Sized
{
type Output;
fn aggregate(data: &[Self]) -> Self::Output;
}
fn main() {}
trait Aggregate
where
Self: Sized
{
type Output;
fn aggregate(data: &[Self]) -> Self::Output;
}
или
trait Aggregate: Sized {
type Output;
fn aggregate(data: &[Self]) -> Self::Output;
}
fn main() {}
trait Aggregate: Sized {
type Output;
fn aggregate(data: &[Self]) -> Self::Output;
}
Sized
trait objects
Забележка - построяването на trait object dyn MyTrait изисква Self: ?Sized
// Ако добавим ограничение на трейта, няма да можем
// да създадем `&dyn Aggregate`
trait Aggregate: Sized {
type Output;
fn score(&self) -> Self::Output;
fn aggregate(data: &[Self]) -> Self::Output;
}
fn main() {}
// Ако добавим ограничение на трейта, няма да можем
// да създадем `&dyn Aggregate`
trait Aggregate: Sized {
type Output;
fn score(&self) -> Self::Output;
fn aggregate(data: &[Self]) -> Self::Output;
}
Sized
trait objects
Забележка - построяването на trait object dyn MyTrait изисква Self: ?Sized
// Ако добавим ограничение на функцията, то ще можем
// да създадем `&dyn Aggregate`, но през trait object-а няма
// да можем да използваме метода `aggregate` - само `score`.
trait Aggregate {
type Output;
fn score(&self) -> Self::Output;
fn aggregate(data: &[Self]) -> Self::Output
where Self: Sized;
}
// Ако искаме да използваме метода `aggregate`, трябва да използваме
// функция с generic параметър
fn my_aggregation1<T: Aggregate + Sized>(data: &[T]) -> T::Output { todo!() }
// Но припомняне, че Sized е имплицитно
fn my_aggregation2<T: Aggregate>(data: &[T]) -> T::Output { todo!() }
fn main() {}
// Ако добавим ограничение на функцията, то ще можем
// да създадем `&dyn Aggregate`, но през trait object-а няма
// да можем да използваме метода `aggregate` - само `score`.
trait Aggregate {
type Output;
fn score(&self) -> Self::Output;
fn aggregate(data: &[Self]) -> Self::Output
where Self: Sized;
}
// Ако искаме да използваме метода `aggregate`, трябва да използваме
// функция с generic параметър
fn my_aggregation1(data: &[T]) -> T::Output { todo!() }
// Но припомняне, че Sized е имплицитно
fn my_aggregation2(data: &[T]) -> T::Output { todo!() }
Boxed
fat pointers
Box<str>~~Stringбез преоразмеряванеBox<[T]>~~Vec<T>без преоразмеряване
Boxed
traits
Box<dyn Trait>- динамично алокиран обект с type erasureBox<T>⟶Box<dyn Trait>, акоT: Trait
let mut log_values: Vec<Box<dyn std::fmt::Debug>> = vec![];
log_values.push(Box::new(5));
log_values.push(Box::new("my stirng"));
let boxed_user = Box::new(User {
/* ... */
});
log_values.push(boxed_user);
println!("{:?}", log_values);
[5, "my stirng", User { name: "Тодор", age: 25 }]
fn main() {
#[derive(Debug)] struct User { name: String, age: i32 }
let mut log_values: Vec> = vec![];
log_values.push(Box::new(5));
log_values.push(Box::new("my stirng"));
let boxed_user = Box::new(User {
name: String::from("Тодор"),
age: 25,
/* ... */
});
log_values.push(boxed_user);
println!("{:?}", log_values);
}
Boxed
traits
- можем да комбинираме това с фиксирани асоциирани типове
Box<dyn Iterator<Item = String>>
Предифиниране на оператори
Предифиниране на оператори
- операторите се дефинират чрез traits
- от модула
std::ops(или отstd::cmp, или други)
let x = a + b;
// се свежда до
let x = std::ops::Add::add(&a, &b);
Предифиниране на оператори
аритметични
Модул std::ops
Add,AddAsignSub,SubAsignMul,MulAsignDiv,DivAsignRem,RemAsignNeg
Предифиниране на оператори
побитови
Модул std::ops
BitAnd,BitAndAsignBitOr,BitOrAsignBitXor,BitXorAsignNotShl,ShlAsignShr,ShrAsign
Предифиниране на оператори
сравнение
Модул std::cmp
PartialEq-- оператори==,!=EqPartialOrd-- оператори<,<=,>,>=Ord
Предифиниране на оператори
други
Модул std::ops
Index,IndexMut-- заa[x]Deref,DerefMut
Предифиниране на оператори
Add изглежда така. Защо?
pub trait Add<Rhs = Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
Associated types vs generic traits
Каква е разликата?
Типаж може да има асоцииран тип
trait MyTrait {
type Output;
fn do_stuff(&self) -> Self::Output;
}
Или да има типов параметър
trait MyTrait<T> {
fn do_stuff(&self) -> T;
}
Associated types vs generic traits
Асоцииран тип
- съществува само един типаж
MyTrait - даден тип
Fooможе да има само една имплементация наMyTrait - за всяка отделна имплементация (
impl MyTrait for Foo) може да се зададе отделен тип заOutput
trait MyTrait {
type Output;
}
impl MyTrait for Foo { type Output = f32; }
impl MyTrait for Bar { type Output = String; }
fn main() {
trait MyTrait {
type Output;
}
impl MyTrait for Foo { type Output = f32; }
impl MyTrait for Bar { type Output = String; }
struct Foo;
struct Bar;
}
Associated types vs generic traits
Типов параметър
- съществуват множество типажи
MyTrait<i32>е съвсем отделен trait отMyTrait<String>
- даден тип
Fooможе да има имплементация за всеки отделен типаж- една имплементация за
MyTrait<i32>, друга заMyTrait<String>
- една имплементация за
trait MyTrait<T> {
}
impl MyTrait<i32> for Foo { }
impl MyTrait<String> for Foo { }
fn main() {
trait MyTrait {
}
impl MyTrait for Foo { }
impl MyTrait for Foo { }
struct Foo;
struct Bar;
}
Associated types vs generic traits
Оператори
Защо аритметичните оператори са generic по Rhs?
- защото може да поддържаме опрации между различни типове
Vector2 * f32 -> Vector2Vector2 * Vector2 -> Vector2
Vector2: Mul<f32>
Vector2: Mul<Vector2>
Associated types vs generic traits
Оператори
Защо за аритметичните оператори Output e асоцииран тип?
- защото за дадени типове
T * Uтипа на резултата обикновенно е фиксиран - няма много логика да имаме
Vector2 * Vector2 -> f32Vector2 * Vector2 -> Vector2
- ако
Outputбеше типов аргумент - при всяко прилагане на*щеше да е задължително да указваме типа на резултатаlet x = vec1 * vec2;-- ambiguouslet x: f32 = vec1 * vec2;
Associated types vs generic traits
Оператори
trait Mul<Rhs = Self> {
type Output;
fn mul(self, rhs: Rhs) -> Self::Output;
}
pub struct Vector2 {
x: f32,
y: f32,
}
// vec * scalar
impl Mul<f32> for Vector2 {
type Output = Vector2;
fn mul(self, scalar: f32) -> Vector2 { Vector2 { x: self.x * scalar, y: self.y * scalar } }
}
// vec * vec
impl Mul<Vector2> for Vector2 {
type Output = Vector2;
fn mul(self, rhs: Vector2) -> Vector2 { Vector2 { x: self.x * rhs.x, y: self.y * rhs.y } }
}
fn main() {}
trait Mul {
type Output;
fn mul(self, rhs: Rhs) -> Self::Output;
}
pub struct Vector2 {
x: f32,
y: f32,
}
// vec * scalar
impl Mul for Vector2 {
type Output = Vector2;
fn mul(self, scalar: f32) -> Vector2 { Vector2 { x: self.x * scalar, y: self.y * scalar } }
}
// vec * vec
impl Mul for Vector2 {
type Output = Vector2;
fn mul(self, rhs: Vector2) -> Vector2 { Vector2 { x: self.x * rhs.x, y: self.y * rhs.y } }
}
Функции и ламбди
TODO: ще добавя слайдовете скоро