Ассоциированный тип
Подробное объяснение на пальцах ассоциированного типа в Rust
Создадим свой трейт с ассоциированным типом:
Пример 1: Трейт Container
#![allow(unused)] fn main() { // Определяем трейт с ассоциированным типом trait Container { type Item; // Ассоциированный тип - "какой-то тип элемента" fn get(&self) -> &Self::Item; fn put(&mut self, value: Self::Item); } // Реализация для структуры, хранящей i32 struct NumberBox { value: i32, } impl Container for NumberBox { type Item = i32; // Конкретизируем: здесь элементы - i32 fn get(&self) -> &Self::Item { &self.value } fn put(&mut self, value: Self::Item) { self.value = value; } } // Реализация для структуры, хранящей String struct StringBox { value: String, } impl Container for StringBox { type Item = String; // Конкретизируем: здесь элементы - String fn get(&self) -> &Self::Item { &self.value } fn put(&mut self, value: Self::Item) { self.value = value; } } }
Пример 2: Трейт Producer
#![allow(unused)] fn main() { trait Producer { type Output; // "Я производлю значения какого-то типа" fn produce(&self) -> Self::Output; } // Реализация для генератора чисел struct NumberGenerator { start: i32, } impl Producer for NumberGenerator { type Output = i32; // Производит i32 fn produce(&self) -> Self::Output { self.start + 42 } } // Реализация для генератора строк struct GreetingGenerator; impl Producer for GreetingGenerator { type Output = String; // Производит String fn produce(&self) -> Self::Output { "Hello!".to_string() } } }
Пример 3: Трейт Converter
#![allow(unused)] fn main() { trait Converter { type Input; type Output; // Два ассоциированных типа! fn convert(value: Self::Input) -> Self::Output; } // Конвертер i32 -> String struct StringConverter; impl Converter for StringConverter { type Input = i32; type Output = String; fn convert(value: Self::Input) -> Self::Output { value.to_string() } } // Конвертер String -> usize struct LengthConverter; impl Converter for LengthConverter { type Input = String; type Output = usize; fn convert(value: Self::Input) -> Self::Output { value.len() } } }
Как это использовать:
fn use_container<C: Container>(container: &C) { let item = container.get(); println!("Item: {:?}", item); } fn use_producer<P: Producer>(producer: &P) where P::Output: std::fmt::Display, // Ограничение на ассоциированный тип { let output = producer.produce(); println!("Produced: {}", output); } fn main() { let num_box = NumberBox { value: 10 }; let str_box = StringBox { value: "hello".to_string() }; use_container(&num_box); // Item: 10 use_container(&str_box); // Item: "hello" let num_gen = NumberGenerator { start: 5 }; let greet_gen = GreetingGenerator; use_producer(&num_gen); // Produced: 47 use_producer(&greet_gen); // Produced: Hello! }
Ключевая идея:
Ассоциированный тип — это "дырка для типа", которую заполняют при реализации трейта:
- В трейте:
type Item— "будет какой-то тип" - В impl:
type Item = КонкретныйТип— "вот именно этот тип"
Чем отличается от дженериков:
#![allow(unused)] fn main() { // С дженериком - можно много реализаций для одного типа trait GenericContainer<T> { fn get(&self) -> &T; } impl GenericContainer<i32> for NumberBox { /* ... */ } impl GenericContainer<String> for NumberBox { /* ... */ } // Две реализации! // С ассоциированным типом - только одна реализация impl Container for NumberBox { type Item = i32; // Только один тип может быть Item! // ... } }
Ассоциированный тип связывает тип элемента с самой реализацией трейта, а не с каждым вызовом метода.