% Преобразования при разыменовании (deref coercions)
Стандартная библиотека Rust реализует особый типаж, Deref. Обычно его
используют, чтобы перегрузить *, операцию разыменования:
use std::ops::Deref; struct DerefExample<T> { value: T, } impl<T> Deref for DerefExample<T> { type Target = T; fn deref(&self) -> &T { &self.value } } fn main() { let x = DerefExample { value: 'a' }; assert_eq!('a', *x); }
Это полезно при написании своих указательных типов. Однако, в языке есть
возможность, связанная с Deref: преобразования при разыменовании. Вот правило:
если есть тип U, и он реализует Deref<Target=T>, значения &U будут
автоматически преобразованы в &T, когда это необходимо. Вот пример:
#![allow(unused)] fn main() { fn foo(s: &str) { // позаимствуем строку на секунду } // String реализует Deref<Target=str> let owned = "Hello".to_string(); // Поэтому, такой код работает: foo(&owned); }
Амперсанд перед значением означает, что мы берём ссылку на него. Поэтому owned
- это
String, а&owned—&String. Поскольку у нас есть реализация типажаimpl Deref<Target=str> for String,&Stringразыменуется в&str, что устраиваетfoo().
Вот и всё. Это правило — одно из немногих мест в Rust, где типы преобразуются
автоматически. Оно позволяет писать гораздо более гибкий код. Например, тип
Rc<T> реализует Deref<Target=T>, поэтому такой код работает:
#![allow(unused)] fn main() { use std::rc::Rc; fn foo(s: &str) { // позаимствуем строку на секунду } // String реализует Deref<Target=str> let owned = "Hello".to_string(); let counted = Rc::new(owned); // Поэтому, такой код работает: foo(&counted); }
Мы всего лишь обернули наш String в Rc<T>. Но теперь мы можем передать
Rc<String> везде, куда мы могли передать String. Сигнатура foo не
поменялась, и работает как с одним, так и с другим типом. Этот пример делает два
преобразования: сначала Rc<String преобразуется в String, а потом String в
&str. Rust сделает столько преобразований, сколько возможно, пока типы не
совпадут.
Другая известная реализация, предоставляемая стандартной библиотекой, это
impl Deref<Target=[T]> for Vec<T>:
#![allow(unused)] fn main() { fn foo(s: &[i32]) { // позаимствуем срез на секунду } // Vec<T> реализует Deref<Target=[T]> let owned = vec![1, 2, 3]; foo(&owned); }
Вектора могут разыменовываться в срезы.
Разыменование и вызов методов
Deref также будет работать при вызове метода. Другими словами, возможен такой
код:
#![allow(unused)] fn main() { struct Foo; impl Foo { fn foo(&self) { println!("Foo"); } } let f = Foo; f.foo(); }
Несмотря на то, что f — это не ссылка, а foo принимает &self, это будет
работать. Более того, все примеры ниже делают одно и то же:
f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();
Методы Foo можно вызывать и на значении типа &&&&&&&&&&&&&&&&Foo, потому что
компилятор сделает столько разыменований, сколько нужно для совпадения типов.
А разыменование использует Deref.