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. Библиография

Замыкания

Помимо именованных функций Rust предоставляет еще и анонимные функции. Анонимные функции, которые имеют связанное окружение, называются 'замыкания'. Они так называются потому что они замыкают свое окружение. Как мы увидим далее, Rust имеет реально крутую реализацию замыканий.

Синтаксис

Замыкания выглядят следующим образом:

let plus_one = |x: i32| x + 1;

assert_eq!(2, plus_one(1));Run

Мы создаем связывание, plus_one, и присваиваем ему замыкание. Аргументы замыкания располагаются между двумя символами |, а телом замыкания является выражение, в данном случае: x + 1. Помните, что { } также является выражением, поэтому тело замыкания может содержать много строк:

let plus_two = |x| {
    let mut result: i32 = x;

    result += 1;
    result += 1;

    result
};

assert_eq!(4, plus_two(2));Run

Обратите внимание, что есть несколько небольших различий между замыканиями и обычными функциями, определенными с помощью fn. Первое отличие состоит в том, что для замыкания мы не должны указывать ни типы аргументов, которые оно принимает, ни тип возвращаемого им значения. Мы можем:

let plus_one = |x: i32| -> i32 { x + 1 };

assert_eq!(2, plus_one(1));Run

Но мы не должны. Почему так? В основном, это было сделано из эргономических соображений (соображений удобства). В то время как для именованных функций явное указание типа является полезным для таких аспектов как документация и вывод типа, типы замыканий редко документируют, поскольку они анонимны. К тому же, они не вызывают «ошибок на расстоянии» (error-at-a-distance), которые могут вызывать именованные функции. Такие ошибки могут возникать, когда локальное изменение (например, в теле одной из функций) вызывает изменение вывода типов. Компилятор пытается подобрать типы в окружающей программе под уже другие типы в изменённой функции, и часто оказывается, что имена имеют другие типы, нежели мы ожидали. В результате происходит ошибка «на расстоянии» — возможно, в другой функции, использующей изменённую.

Второе отличие — синтаксис очень похож, но все же немного отличается. Мы добавили пробелы здесь, чтобы было нагляднее:

fn  plus_one_v1   (x: i32 ) -> i32 { x + 1 }
let plus_one_v2 = |x: i32 | -> i32 { x + 1 };
let plus_one_v3 = |x: i32 |          x + 1  ;Run

Есть небольшие различия, но принцип аналогичен.

Замыкания и их окружение

Замыкания называются так потому, что они 'замыкают свое окружение.' Это выглядит следующим образом:

let num = 5;
let plus_num = |x: i32| x + num;

assert_eq!(10, plus_num(5));Run

Это замыкание, plus_num, ссылается на связанную с помощью оператора let переменную num, расположенную в своей области видимости. Если говорить более конкретно, то оно заимствует связывание. Если мы сделаем что-то, что противоречило бы связыванию, то получим ошибку. Например этот код:

let mut num = 5;
let plus_num = |x: i32| x + num;

let y = &mut num;Run

Который выдаст следующие ошибки:

error: cannot borrow `num` as mutable because it is also borrowed as immutable
    let y = &mut num;
                 ^~~
note: previous borrow of `num` occurs here due to use in closure; the immutable
  borrow prevents subsequent moves or mutable borrows of `num` until the borrow
  ends
    let plus_num = |x| x + num;
                   ^~~~~~~~~~~
note: previous borrow ends here
fn main() {
    let mut num = 5;
    let plus_num = |x| x + num;
    
    let y = &mut num;
}
^

Подробное и к тому же полезное сообщение об ошибке! Как говорится в этом сообщении, мы не можем получить изменяемый заем переменной num потому что замыкание уже заимствует его. Если же мы обеспечим выход замыкания из области видимости, то мы сможем:

let mut num = 5;
{
    let plus_num = |x: i32| x + num;

} // plus_num goes out of scope, borrow of num ends

let y = &mut num;Run

Однако, Rust также может забирать право владения и перемещать свое окружение, если этого требует замыкание:

let nums = vec![1, 2, 3];

let takes_nums = || nums;

println!("{:?}", nums);Run

Этот код выдаст:

note: `nums` moved into closure environment here because it has type
  `[closure(()) -> collections::vec::Vec<i32>]`, which is non-copyable
let takes_nums = || nums;
                    ^~~~~~~

Vec<T> обладает правом владения на свое содержимое, и поэтому, когда мы ссылаемся на него в нашем замыкании, мы должны забрать право владения на nums. Это тоже самое, как если бы мы передавали nums в функцию, которая забирала бы право владения на него.

Перемещающие замыкания (move closures)

Мы можем заставить наше замыкание забирать право владения на свое окружение с помощью ключевого слова move:

let num = 5;

let owns_num = move |x: i32| x + num;Run

Теперь, когда указано ключевое слово move, переменные следуют нормальной семантике перемещения. В данном примере 5 реализует Copy, поэтому owns_num становится владельцем копии num. Так в чем же разница?

let mut num = 5;

{
    let mut add_num = |x: i32| num += x;

    add_num(5);
}

assert_eq!(10, num);Run

Итак, в этом примере наше замыкание принимает изменяемую ссылку на num. Затем, когда мы вызываем замыкание add_num, то, как мы и ожидали, оно изменяет значение внутри. Нам также необходимо объявить add_num как mut, потому что оно изменяет свое окружение.

Если же мы будем использовать move замыкание, то получим следующие отличия:

let mut num = 5;

{
    let mut add_num = move |x: i32| num += x;

    add_num(5);
}

assert_eq!(5, num);Run

Мы всего лишь получаем 5. Вместо того, чтобы получать изменяемый заем на num, мы получаем право владения на копию.

Вот еще один способ думать о move замыканиях: они предоставляют замыкание со своим собственным фреймом стека. Без move замыкание может быть связано с фреймом стека, который его создал, в то время как move замыкание содержит свой собственный фрейм стека. Это означает, например, что вы не можете вернуть не move замыкание из функции.

Но прежде чем говорить о получении в качестве аргумента и возвращении замыкания, мы должны поговорить о том, как реализуются замыкания. Как системный язык программирования, Rust дает вам кучу контроля над тем, что делает ваш код, и замыкания не являются исключением.

Реализация замыканий

Реализация замыканий в Rust немного отличается от других языков. Фактически, она представляет из себя просто синтаксический сахар для типажей. Перед тем как читать дальше, настоятельно рекомендуем изучить главу Типажи, а также главу Типажи-объекты, в которой говорится о типажах-объектах.

Изучили? Хорошо.

Ключ к пониманию того, как замыкания работают изнутри звучит немного странно: использование () для вызова функции, как например foo(), представляет собой перегружаемую операцию. Исходя из этого, все остальное встает на свои места. В Rust мы используем систему типажей для перегрузки операций. Вызов функций не является исключением. Существуют три отдельных типажа для их перегрузки:

pub trait Fn<Args> : FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

pub trait FnMut<Args> : FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

pub trait FnOnce<Args> {
    type Output;

    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}Run

Вы можете заметить некоторые различия между этими типажами, но есть одно главное различие — self: Fn принимает &self, FnMut принимает &mut self, FnOnce принимает self. Это покрывает все три вида self с помощью обычного синтаксиса вызова методов. Мы разделили их на три типажа, вместо того, чтобы иметь один. Это дает нам большее количество контроля над тем, какого вида замыкания мы можем принять.

Использование || {} при создании замыканий является синтаксическим сахаром для этих трех типажей. Rust будет генерировать структуру для окружения, реализующую (impl) соответствующий типаж, а затем использовать его.

Передача замыканий в качестве аргументов

Теперь, когда мы знаем, что замыкания являются типажами, получается, что мы уже знаем, как принимать и возвращать замыкания: как и любой другой типаж!

Это также означает, что мы можем выбирать между статической и динамической диспетчеризацией. Во-первых, давайте напишем функцию, которая принимает что-то вызываемое, вызывает это что-то и возвращает результат:

fn call_with_one<F>(some_closure: F) -> i32
    where F : Fn(i32) -> i32 {

    some_closure(1)
}

let answer = call_with_one(|x| x + 2);

assert_eq!(3, answer);Run

Мы передаем наше замыкание |x| x + 2, в функцию call_with_one. Она же делает то, о чем говорит ее название: вызывает замыкание, передавая ему 1 в качестве аргумента.

Давайте рассмотрим сигнатуру функции call_with_one более подробно:

fn call_with_one<F>(some_closure: F) -> i32Run

Мы принимаем один параметр, который имеет тип F. Мы также возвращаем i32. Эта часть не интересна. Следующим важным моментом является:

    where F : Fn(i32) -> i32 {Run

Так как Fn является типажом, мы можем связать с ним наш обобщенный параметр. В этом примере, замыкание принимает i32 в качестве аргумента и возвращает i32, поэтому связывание, которое мы используем, выглядит так: Fn(i32) -> i32.

Здесь есть еще один ключевой момент: так как мы ограничиваем обобщённый параметр с помощью типажа, то будет применена мономорфизация, и поэтому в замыкании будет использоваться статическая диспетчеризация. Это довольно лаконично (аккуратно). Во многих языках для замыканий по существу используется выделение памяти в куче, и поэтому всегда будет использоваться динамическая диспетчеризация. В Rust мы можем выделить память для окружения замыкания в стеке и использовать статическую диспетчеризацию вызова. Это случается довольно часто с итераторами и их адаптерами, которые нередко принимают замыкания в качестве аргументов.

Конечно, если нам нужна динамическая диспетчеризация, мы также можем использовать и ее. Обычно для этого случая используется типаж-объект:

fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
    some_closure(1)
}

let answer = call_with_one(&|x| x + 2);

assert_eq!(3, answer);Run

Теперь наша функция в качетве аргумента принимает типаж-объект &Fn. Поэтому мы должны создать ссылку на замыкание, а затем передать ее в функцию call_with_one, для этого мы используем &||.

Возврат замыканий

Что очень характерно для кода в функциональном стиле — возвращать замыкания в различных ситуациях. Если вы попытаетесь вернуть замыкание, то можете столкнуться с ошибкой. Сперва это может показаться странным, но мы с этим разберемся. Вот как вы, наверное, попытаетесь вернуть замыкание из функции:

fn factory() -> (Fn(i32) -> i32) {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);Run

Это выдаст следующие длинные, взаимосвязанные ошибки:

error: the trait `core::marker::Sized` is not implemented for the type
`core::ops::Fn(i32) -> i32` [E0277]
fn factory() -> (Fn(i32) -> i32) {
                ^~~~~~~~~~~~~~~~
note: `core::ops::Fn(i32) -> i32` does not have a constant size known at compile-time
fn factory() -> (Fn(i32) -> i32) {
                ^~~~~~~~~~~~~~~~
error: the trait `core::marker::Sized` is not implemented for the type `core::ops::Fn(i32) -> i32` [E0277]
let f = factory();
    ^
note: `core::ops::Fn(i32) -> i32` does not have a constant size known at compile-time
let f = factory();
    ^

Для того чтобы вернуть что-то из функции, Rust должен знать, какой размер имеет тип возвращаемого значения. Но так как Fn является типажом, то в качестве него могут выступать совершенно разные объекты, с разными размерами: много различных типов могут реализовать Fn. Самый простой способ передать что-то неопределенного размера — передать ссылку на это что-то, так как ссылки имеют известный размер. Таким образом, следовало бы написать так:

fn factory() -> &(Fn(i32) -> i32) {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);Run

Но тогда мы получим другую ошибку:

error: missing lifetime specifier [E0106]
fn factory() -> &(Fn(i32) -> i32) {
                ^~~~~~~~~~~~~~~~~

Верно. Так как у нас используется ссылка, то мы должны задать ее время жизни. Так наша функция factory() не принимает никаких аргументов, то элизия (сокрытие) здесь не уместна. Какое время жизни мы должны выбрать? 'static:

fn factory() -> &'static (Fn(i32) -> i32) {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);Run

Но мы получим еще ошибку:

error: mismatched types:
 expected `&'static core::ops::Fn(i32) -> i32`,
    found `[closure <anon>:7:9: 7:20]`
(expected &-ptr,
    found closure) [E0308]
         |x| x + num
         ^~~~~~~~~~~

Эта ошибка сообщает нам, что ожидается использование &'static Fn(i32) -> i32, а используется [closure <anon>:7:9: 7:20]. Подождите, что?

Поскольку каждое замыкание (в индивидуальном порядке) генерирует свою собственную struct для окружения и реализует Fn и компанию, то эти типы являются анонимными. Они существуют исключительно для этого замыкания. Поэтому Rust показывает их как closure <anon>, а не в виде какого-то автоматически сгенерированного имени.

Но почему же наше замыкание не реализует &'static Fn? Как мы обсуждали ранее, замыкание заимствует свое окружение. И в этом случае наше окружение представляет собой выделенную в стеке память, содержащую значение связанной переменной num - 5. Из-за этого заем имеет срок жизни фрейма стека. Так что, когда мы вернем это замыкание, то вызов функции будет завершен, а фрейм стека уйдет, и наше замыкание захватит окружение, содержащее в памяти мусор!

Так что же делать? Этот код почти работает:

fn factory() -> Box<Fn(i32) -> i32> {
    let num = 5;

    Box::new(|x| x + num)
}
let f = factory();

let answer = f(1);
assert_eq!(6, answer);Run

Мы используем типаж-объект, полученный в результате упаковки (Box) типажа Fn. И остаётся только одна, последняя проблема:

error: closure may outlive the current function, but it borrows `num`,
which is owned by the current function [E0373]
Box::new(|x| x + num)
         ^~~~~~~~~~~

Мы все еще по-прежнему ссылаемся на родительский фрейм стека. С этим последним исправлением мы сможем наконец выполнить нашу задачу:

fn factory() -> Box<Fn(i32) -> i32> {
    let num = 5;

    Box::new(move |x| x + num)
}
let f = factory();

let answer = f(1);
assert_eq!(6, answer);Run

Благодаря изменению внутреннего замыкания на move Fn будет создаваться новый фрейм стека для нашего замыкания. А благодаря упаковке (Box) замыкания, получается известный размер возвращаемого значения, и позволяет ему избежать (быть независимым от) нашего фрейма стека.