Каковы точные правила автоматического разыменования (auto-dereferencing) в Rust?
Точные правила автоматического разыменования в Rust
Отличный вопрос! Давайте разберемся с этим механизмом, который часто сбивает с толку новичков. Rust применяет автоматическое разыменование (auto-dereferencing) только в двух конкретных ситуациях:
- При вызове методов (через оператор
.) - При проверке реализации трейтов (для вызова методов трейта)
В вашем примере работает именно первое правило. Когда вы пишете r2.distance_from_origin(), компилятор смотрит: у типа &&Point нет метода distance_from_origin. Тогда он начинает разыменовывать: &Point — тоже нет, Point — есть! Метод найден, и компилятор автоматически вставляет необходимое количество разыменований.
Как именно работает этот процесс?
Алгоритм поиска метода для выражения value.method() следующий:
- Проверить, есть ли метод у типа
T(гдеT— типvalue) - Если нет — проверить у
&T(ссылку наT) - Если нет — проверить у
&mut T - Если нет — разыменовать
TдоU(через трейтDeref) и повторить шаги 1-3 дляU - Повторять шаг 4, пока не найдется подходящий метод или не закончатся разыменования
Важно: шаги 1-3 проверяются на каждом уровне разыменования. Вот как это работает для &&Point:
Шаг 1: Проверяем &&Point — метода нет
Шаг 2: Проверяем &(&&Point) — это &&&Point, метода нет
Шаг 3: Проверяем &mut (&&Point) — это &mut &&Point, метода нет
Шаг 4: Разыменовываем &&Point до &Point (через Deref)
Шаг 1: Проверяем &Point — метода нет
Шаг 2: Проверяем &(&Point) — это &&Point, метода нет
Шаг 3: Проверяем &mut &Point — метода нет
Шаг 4: Разыменовываем &Point до Point
Шаг 1: Проверяем Point — ЕСТЬ метод! Используем его.
Авто-разыменование для операторов
Операторы в Rust (например, +, -, *, /) не подвергаются автоматическому разыменованию. Они реализуются через трейты (например, Add, Mul), и компилятор проверяет, реализован ли трейт для данного типа. Но сам процесс поиска реализации трейта тоже использует авто-разыменование!
fn main() {
let a = 5;
let b = &10;
// println!("{}", a + b); // Ошибка! int + &int не работает
println!("{}", a + *b); // Надо явно разыменовать: 5 + 10 = 15
}
Исключение: оператор * для разыменования
Когда вы используете * для разыменования ссылки, Rust не применяет авто-разыменование. Он просто делает одно разыменование. Но если у вас &&T, то * даст &T, а не T:
fn main() {
let x = 42;
let r = &&x;
let y = *r; // y: &i32, а не i32!
let z = **r; // z: i32
}
Авто-разыменование с трейтами
Это работает и для трейтов. Если у вас есть Box<dyn SomeTrait>, и вы вызываете метод трейта, Rust автоматически разыменует Box до dyn SomeTrait и найдет метод:
trait Speak {
fn speak(&self);
}
impl Speak for String {
fn speak(&self) {
println!("{}", self);
}
}
fn main() {
let s = Box::new(String::from("Привет!"));
s.speak(); // Работает! Авто-разыменование Box -> String
}
Резюме
- Авто-разыменование применяется только при вызове методов (через
.) и при поиске реализации трейтов - Для операторов (
+,*и т.д.) авто-разыменования нет — надо разыменовывать явно - Оператор
*делает ровно одно разыменование, не больше - Процесс повторяется рекурсивно, пока не найдется подходящий метод
- На каждом уровне проверяются: сам тип, ссылка на него и мутабельная ссылка
Дополнение: как работает авто-разыменование с изменяемыми ссылками
Важно понимать, что авто-разыменование работает и с &mut T, но есть нюансы. Если у вас &mut T, и метод принимает &self, компилятор может автоматически взять неизменяемую ссылку (поскольку &mut T можно неявно привести к &T). Но если метод принимает &mut self, то компилятор будет искать &mut T на каждом уровне разыменования.
Пример с изменяемыми ссылками:
struct Counter {
value: i32,
}
impl Counter {
fn increment(&mut self) {
self.value += 1;
}
fn get(&self) -> i32 {
self.value
}
}
fn main() {
let mut c = Counter { value: 0 };
let r1 = &mut c;
let r2 = &mut &mut c;
r1.increment(); // Работает: &mut Counter -> &mut self
r2.increment(); // Работает: &mut &mut Counter -> &mut Counter -> &mut self
// А вот так не сработает:
// let r3 = &&mut c;
// r3.increment(); // Ошибка! &&mut Counter не может дать &mut Counter
}
Обратите внимание: если у вас двойная неизменяемая ссылка (&&T), вы не можете получить &mut T через авто-разыменование, потому что для этого нужно было бы нарушить правила заимствования. Компилятор умный и блокирует такие попытки.
Ещё один важный момент: авто-разыменование не работает в обратную сторону. Если у вас T, компилятор не будет автоматически брать ссылку на него, чтобы найти метод, ожидающий &self — это уже работа механизма автоматического взятия ссылки (auto-referencing), который является отдельной темой.
Ответов пока нет.
Войдите чтобы оставить ответ