Захват

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

  • по ссылке: &T
  • по изменяемой ссылке: &mut T
  • по значению: T

Преимущественно, они захватывают переменные по ссылке, и используют другие способы только там, где это необходимо.

fn main() {
    use std::mem;
    
    let color = "green";

    // Замыкание для вывода `color`, которое немедленно заимствует (`&`)
    // `color` и сохраняет замыкание в переменной `print`. `color` будет оставаться
    // заимствованным до тех пор, пока `print` не будет использован в последний раз.
    //
    // `println!` принимает аргументы только по неизменяемым ссылкам, поэтому он не накладывает
    // дополнительных ограничений.
    let print = || println!("`color`: {}", color);

    // Вызываем замыкание, использующее заимствование.
    print();

    // `color` может быть неизменяемо заимствован, так как замыкание
    // держит только неизменяемую ссылку на `color`.
    let _reborrow = &color;
    print();

    // Перемещение или перезанятие возможно после последнего использования `print`
    let _color_moved = color;


    let mut count = 0;
    // Замыкание для увеличения `count` может принимать как `&mut count`, так и `count`,
    // но использование `&mut count` накладывает меньше ограничений, так что
    // замыкание выбирает первый способ, т.е. немедленно заимствует `count`.
    //
    // `inc` должен быть `mut`, поскольку внутри него хранится `&mut`.
    // Таким образом, вызов замыкания изменяет его, что недопустимо без `mut`.
    let mut inc = || {
        count += 1;
        println!("`count`: {}", count);
    };

    // Вызываем замыкание, использующее изменяемое заимствование.
    inc();

    // Замыкание продолжает изменяемо заимствовать `count`, так как оно используется дальше.
    // Попытка перезанять приведёт к ошибке.
    // let _reborrow = &count;
    // ^ TODO: попробуйте раскомментировать эту строку.
    inc();

    // Замыкание больше не заимствует `&mut count`. Так что теперь
    // при перезаимствовании ошибок не будет.
    let _count_reborrowed = &mut count;

    
    // Некопируемый тип.
    let movable = Box::new(3);

    // `mem::drop` требует `T`, так что захват производится по значению.
    // Копируемый тип будет скопирован в замыкание, оставив оригинальное
    // значение без изменения. Некопируемый тип должен быть перемещён, так что
    // `movable` немедленно перемещается в замыкание.
    let consume = || {
        println!("`movable`: {:?}", movable);
        mem::drop(movable);
    };

    // `consume` поглощает переменную, так что оно может быть вызвано только один раз.
    consume();
    // consume();
    // ^ TODO: Попробуйте раскомментировать эту строку.
}

Использование move перед вертикальными линиями позволяет получить владение над захваченными переменными:

fn main() {
    // Vec` не поддерживает копирование.
    let haystack = vec![1, 2, 3];

    let contains = move |needle| haystack.contains(needle);

    println!("{}", contains(&1));
    println!("{}", contains(&4));

    // println!("Количество элементов {} в векторе", haystack.len());
    // ^ Уберите комментарий с этой строки и в результате получите ошибку компиляции,
    // потому что анализатор заимствований не позволяет использовать
    // переменную после передачи владения.
    
    // Удалите `move` у замыкания и _haystack_ будет заимствован по неизменяемой
    // ссылке, и удалённый комментарий теперь не вызывает ошибки.
}

Смотрите также:

Box и std::mem::drop