Изучение замыканий Rust и поведения захвата переменных

Beginner

This tutorial is from open-source community. Access the source code

Введение

В этом лабораторном задании мы исследуем замыкания в Rust и их поведение захвата переменных, которое позволяет им захватывать переменные по ссылке, изменяемой ссылке или значению, в зависимости от требований замыкания.

Примечание: Если в лабораторном задании не указано имя файла, вы можете использовать любое имя файла, которое хотите. Например, вы можете использовать main.rs, скомпилировать и запустить его с помощью rustc main.rs &&./main.

Захват переменных

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

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

Они предпочитают захватывать переменные по ссылке и опускаются на более низкий уровень только при необходимости.

fn main() {
    use std::mem;

    let color = String::from("green");

    // Замыкание для печати `color`, которое сразу заимствует (`&`) `color` и
    // сохраняет заимствование и замыкание в переменной `print`. Она будет
    // заимствоваться до последнего использования `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`.
    //
    // Нужен `mut` для `inc`, потому что внутри хранится `&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!("There're {} elements in vec", haystack.len());
    // ^ Раскомментирование строки выше приведет к ошибке компиляции,
    // потому что проверщик заимствований не позволяет повторно использовать
    // переменную после ее перемещения.

    // Удаление `move` из сигнатуры замыкания приведет к тому, что замыкание
    // будет заимствовать переменную _haystack_ неизменяемым образом, поэтому
    // _haystack_ по-прежнему доступна и раскомментирование строки выше не
    // вызовет ошибку.
}

Резюме

Поздравляем! Вы завершили лабораторную работу по захвату переменных. Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.