История вопроса
В Rust типы часто инкапсулируют другие значения (например, Box, Rc), и разработчикам хотелось прозрачного обращения к вложенным значениям, как с указателями в C++. Был добавлен trait Deref (и DerefMut) и специальная логика auto-deref для удобного обращения через методы и операторы.
Проблема
Путаница между тем, когда и как автоматически разворачиваются указатели и smart pointer'ы, приводит к ошибкам компиляции, неожиданному поведению методов, а иногда даже к неоптимальному коду из-за лишних копирований или borrow'ов.
Решение
Trait Deref позволяет описать своё правило разыменования (например, Box<T> ведет себя как T благодаря реализации Deref). Система auto-deref пытается "развернуть" ссылки на Deref'ные типы при обращении к методам и операторам. Это позволяет писать:
use std::rc::Rc; fn print_len(s: &str) { println!("{}", s.len()); } let s: Rc<String> = Rc::new("hello".into()); print_len(&s); // auto-deref: &Rc<String> -> &String -> &str
Ключевые особенности:
*, а также при сопоставлении ссылочных типовПроисходит ли auto-deref всегда, когда этого "ожидает" разработчик?
Нет: deref coercion происходит только при вызове методов либо когда требуется ссылка на другой тип, указанный в сигнатуре через Deref Target. Она не работает во всех выражениях, например при pattern matching.
Может ли deref coercion сломать правила borrowing и владения?
Нет. Deref coercion полностью соблюдает правила borrowing — невозможно получить два mutable-ссылки через Deref, нарушая безопасность владения Rust.
Возможен ли auto-deref от &T к &U, если T: Deref<Target=U> и оба типа не связаны явно?
Да, при вызове функции, ожидающей &U, передача &T, где T реализует Deref<Target=U>, приведёт к автоматическому преобразованию.
Пример кода:
struct Wrapper(String); impl std::ops::Deref for Wrapper { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } fn takes_str(s: &str) {} let w = Wrapper("mytext".into()); takes_str(&w); // auto-deref: &Wrapper -> &String -> &str
Разработчик реализовал свой smart pointer, но не добавил реализацию Deref, ожидая, что его тип будет вести себя как обычное значение.
Плюсы:
Тип комилируется и можно явно обращаться к вложенному значению.
Минусы:
Прекращает работать auto-deref при вызове методов, неудобство при использовании функций stdlib и сторонних библиотек.
Своя структура-обёртка реализует Deref и DerefMut аналогично Box, seamless интеграция со всеми стандартными функциями.
Плюсы:
Удобство, прозрачная работа большинства функций языка, чистота и мягкая интеграция с остальным кодом.
Минусы:
Риск переусложнить интерфейс Deref, если target-тип неочевиден.