1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Преобразования при разыменовании (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);
}Run

Это полезно при написании своих указательных типов. Однако, в языке есть возможность, связанная с Deref: преобразования при разыменовании. Вот правило: если есть тип U, и он реализует Deref<Target=T>, значения &U будут автоматически преобразованы в &T, когда это необходимо. Вот пример:

fn foo(s: &str) {
    // позаимствуем строку на секунду
}

// String реализует Deref<Target=str>
let owned = "Hello".to_string();

// Поэтому, такой код работает:
foo(&owned);Run

Амперсанд перед значением означает, что мы берём ссылку на него. Поэтому owned - это String, а &owned — &String. Поскольку у нас есть реализация типажа impl Deref<Target=str> for String, &String разыменуется в &str, что устраивает foo().

Вот и всё. Это правило — одно из немногих мест в Rust, где типы преобразуются автоматически. Оно позволяет писать гораздо более гибкий код. Например, тип Rc<T> реализует Deref<Target=T>, поэтому такой код работает:

use std::rc::Rc;

fn foo(s: &str) {
    // позаимствуем строку на секунду
}

// String реализует Deref<Target=str>
let owned = "Hello".to_string();
let counted = Rc::new(owned);

// Поэтому, такой код работает:
foo(&counted);Run

Мы всего лишь обернули наш String в Rc<T>. Но теперь мы можем передать Rc<String> везде, куда мы могли передать String. Сигнатура foo не поменялась, и работает как с одним, так и с другим типом. Этот пример делает два преобразования: сначала Rc<String преобразуется в String, а потом String в &str. Rust сделает столько преобразований, сколько возможно, пока типы не совпадут.

Другая известная реализация, предоставляемая стандартной библиотекой, это impl Deref<Target=[T]> for Vec<T>:

fn foo(s: &[i32]) {
    // позаимствуем срез на секунду
}

// Vec<T> реализует Deref<Target=[T]>
let owned = vec![1, 2, 3];

foo(&owned);Run

Вектора могут разыменовываться в срезы.

Разыменование и вызов методов

Deref также будет работать при вызове метода. Другими словами, возможен такой код:

struct Foo;

impl Foo {
    fn foo(&self) { println!("Foo"); }
}

let f = Foo;

f.foo();Run

Несмотря на то, что f — это не ссылка, а foo принимает &self, это будет работать. Более того, все примеры ниже делают одно и то же:

f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();Run

Методы Foo можно вызывать и на значении типа &&&&&&&&&&&&&&&&Foo, потому что компилятор сделает столько разыменований, сколько нужно для совпадения типов. А разыменование использует Deref.