Главная страница
Навигация по странице:

  • Листинг 13-3: Попытка вызова замыкания, типы которого выводятся из двух разных типов

  • Захват ссылок или передача владения

  • Листинг 13-4: Определение и вызов замыкания, которое захватывает неизменяемую ссылку

  • Листинг 13-5. Определение и вызов замыкания, захватывающего изменяемую ссылку

  • Листинг 13-6: Использование move для принуждения замыкания потока принять на себя владение

  • Перемещение захваченных значений из замыканий и трейты

  • Листинг 13-7: Использование sort_by_key для сортировки прямоугольников по ширине

  • Листинг 13-8: Попытка использовать замыкание FnOnce с

  • Листинг 13-9: Использование замыкания FnMut с sort_by_key разрешено

  • Язык программирования Rust


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница34 из 62
    1   ...   30   31   32   33   34   35   36   37   ...   62
    Листинг 13-2: Добавление необязательных аннотаций типов параметров и возвращаемых значений в
    замыкании
    С добавлением аннотаций типов синтаксис замыканий выглядит более похожим на синтаксис функций. Здесь мы определяем функцию, которая добавляет 1 к своему параметру, и замыкание, которое имеет такое же поведение, для сравнения. Мы добавили несколько пробелов, чтобы выровнять соответствующие части. Это показывает, что синтаксис замыкания похож на синтаксис функции, за исключением использования труб (вертикальная черта) и количества необязательного синтаксиса:
    В первой строке показано определение функции, а во второй - полностью аннотированное определение замыкания. В третьей строке мы удаляем аннотации типов из определения замыкания. В четвёртой строке мы убираем скобки, которые являются необязательными, поскольку тело замыкания имеет только одно выражение.
    Это все правильные определения, которые будут иметь одинаковое поведение при let expensive_closure = |num: u32
    | -> u32
    { println!
    (
    "calculating slowly..."
    ); thread::sleep(Duration::from_secs(
    2
    )); num
    }; fn add_one_v1
    (x: u32
    ) -> u32
    { x +
    1
    } let add_one_v2 = |x: u32
    | -> u32
    { x +
    1
    }; let add_one_v3 = |x| { x +
    1
    }; let add_one_v4 = |x| x +
    1
    ;
    вызове. Строки add_one_v3
    и add_one_v4
    требуют, чтобы замыкания были вычислены до компиляции, поскольку типы будут выведены из их использования. Это похоже на let v
    = Vec::new();
    , когда в
    Vec необходимо вставить либо аннотации типов, либо значения некоторого типа, чтобы Rust смог вывести тип.
    Для определений замыкания компилятор выводит один конкретный тип для каждого из параметров и для возвращаемого значения. Например, в листинге 13-3 показано определение короткого замыкания, которое просто возвращает значение, полученное в качестве параметра. Это замыкание не очень полезно, кроме как для целей данного примера. Обратите внимание, что мы не добавили в определение никаких аннотаций типов. Поскольку аннотаций типов нет, мы можем вызвать замыкание с любым типом,
    что мы и сделали в первый раз с
    String
    . Если затем мы попытаемся вызвать example_closure с целым числом, мы получим ошибку.
    Файл : src/main.rs
    Листинг 13-3: Попытка вызова замыкания, типы которого выводятся из двух разных типов
    Компилятор вернёт нам вот такую ошибку:
    При первом вызове example_closure со значением
    String компилятор определяет тип x
    и возвращаемый тип замыкания как
    String
    . Эти типы затем фиксируются в замыкании в example_closure
    , и мы получаем ошибку типа при следующей попытке использовать другой тип с тем же замыканием.
    Захват ссылок или передача владения
    Замыкания могут захватывать значения из своего окружения тремя способами, которые непосредственно соответствуют трём способам, которыми функция может принимать let example_closure = |x| x; let s = example_closure(
    String
    ::from(
    "hello"
    )); let n = example_closure(
    5
    );
    $
    cargo run
    Compiling closure-example v0.1.0 (file:///projects/closure-example) error[E0308]: mismatched types
    -->
    src/main.rs:5:29
    |
    5 | let n = example_closure(5);
    | ^- help: try using a conversion method:
    `.to_string()`
    | |
    | expected struct `String`, found integer
    For more information about this error, try `rustc --explain E0308`. error: could not compile `closure-example` due to previous error
    параметр: неизменное заимствование, мутабельное заимствование и принятие права собственности. Замыкание будет решать, какой из этих способов использовать,
    основываясь на том, что тело функции делает с полученными значениями.
    В листинге 13-4 мы определяем замыкание, которое захватывает неизменяемую ссылку на вектор с именем list
    , поскольку неизменяемая ссылка нужна только для печати значения:
    Файл : src/main.rs
    Листинг 13-4: Определение и вызов замыкания, которое захватывает неизменяемую ссылку
    Этот пример также иллюстрирует, что переменная может связываться с определением замыкания, и мы можем позже вызвать замыкание, используя имя переменной и круглые скобки, как если бы имя переменной было именем функции.
    Поскольку мы можем одновременно иметь несколько неизменяемых ссылок на list
    , list по-прежнему доступен из кода до определения замыкания, после определения замыкания, но до вызова замыкания, и после вызова замыкания. Этот код компилируется, выполняется и печатается:
    Затем в листинге 13.5 мы меняем тело замыкания, чтобы оно добавляло элемент в вектор list
    . Теперь замыкание фиксирует изменяемую ссылку:
    Файл : src/main.rs fn main
    () { let list = vec!
    [
    1
    ,
    2
    ,
    3
    ]; println!
    (
    "Before defining closure: {:?}"
    , list); let only_borrows = || println!
    (
    "From closure: {:?}"
    , list); println!
    (
    "Before calling closure: {:?}"
    , list); only_borrows(); println!
    (
    "After calling closure: {:?}"
    , list);
    }
    $
    cargo run
    Compiling closure-example v0.1.0 (file:///projects/closure-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
    Running `target/debug/closure-example`
    Before defining closure: [1, 2, 3]
    Before calling closure: [1, 2, 3]
    From closure: [1, 2, 3]
    After calling closure: [1, 2, 3]

    Листинг 13-5. Определение и вызов замыкания, захватывающего изменяемую ссылку
    Этот код компилируется, запускается и печатает:
    Обратите внимание, что между определением и вызовом замыкания borrows_mutably больше нет println!
    : когда определяется borrows_mutably
    , оно захватывает мутабельную ссылку на list
    . После вызова замыкания мы больше не используем его,
    поэтому заимствование mutable заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для печати недопустимо, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы.
    Попробуйте добавить туда println!
    и посмотрите, какое сообщение об ошибке вы получите!
    Если вы хотите заставить замыкание взять владение значениями, которые оно использует в окружении, даже если тело замыкания не требует владения, вы можете использовать ключевое слово move перед списком параметров.
    Эта техника в основном полезна при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о параллелизме, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово move
    . В листинге 13-6
    показан код из листинга 13-4, модифицированный для печати вектора в новом потоке, а не в основном потоке:
    Файл : src/main.rs fn main
    () { let mut list = vec!
    [
    1
    ,
    2
    ,
    3
    ]; println!
    (
    "Before defining closure: {:?}"
    , list); let mut borrows_mutably = || list.push(
    7
    ); borrows_mutably(); println!
    (
    "After calling closure: {:?}"
    , list);
    }
    $
    cargo run
    Compiling closure-example v0.1.0 (file:///projects/closure-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
    Running `target/debug/closure-example`
    Before defining closure: [1, 2, 3]
    After calling closure: [1, 2, 3, 7]

    Листинг 13-6: Использование
    move
    для принуждения замыкания потока принять на себя владение
    list
    Мы порождаем новый поток, передавая ему в качестве аргумента замыкание для выполнения. Тело замыкания распечатывает список. В листинге 13-4 замыкание захватило list только с помощью неизменяемой ссылки, потому что это минимально необходимый доступ к list для его печати. В этом примере, несмотря на то, что тело замыкания по-прежнему требует только неизменяемой ссылки, нам нужно указать, что list должен быть перемещён в замыкание, поместив ключевое слово move в начало определения замыкания. Новый поток может завершиться раньше, чем завершится основной поток, или основной поток может завершиться первым. Если основной поток сохранил владение list
    , но завершился раньше нового потока и сбросил list
    ,
    неизменяемая ссылка в потоке будет недействительной. Поэтому компилятор требует,
    чтобы list был перемещён в замыкание, переданное новому потоку, чтобы ссылка была действительной. Попробуйте убрать ключевое слово move или использовать list в основном потоке после определения замыкания и посмотрите, какие ошибки компилятора вы получите!
    Перемещение захваченных значений из замыканий и трейты Fn
    После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается в замыкание), код в теле замыкания определяет, что происходит со ссылками или значениями, в момент последующего выполнения замыкания (тем самым влияя на то, что перемещается из
    замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды.
    То, как замыкание получает и обрабатывает значения из среды, влияет на то, какие трейты реализует замыкание, а трейты - это то, как функции и структуры могут указывать,
    какие типы замыканий они могут использовать. Замыкания автоматически реализуют один, два или все три из этих
    Fn признаков, аддитивным образом, в зависимости от того,
    как тело замыкания обрабатывает значения:
    1.
    FnOnce применяется к замыканиям, которые могут быть вызваны один раз. Все замыкания реализуют по крайней мере этот трейт, потому что все замыкания могут use std::thread; fn main
    () { let list = vec!
    [
    1
    ,
    2
    ,
    3
    ]; println!
    (
    "Before defining closure: {:?}"
    , list); thread::spawn(
    move
    || println!
    (
    "From thread: {:?}"
    , list))
    .join()
    .unwrap();
    }
    быть вызваны. Замыкание, которое перемещает захваченные значения из своего тела, реализует только
    FnOnce и ни один из других признаков
    Fn
    , потому что оно может быть вызвано только один раз.
    2.
    FnMut применяется к замыканиям, которые не перемещают захваченные значения из своего тела, но могут изменять захваченные значения. Эти закрытия могут вызываться более одного раза.
    3.
    Fn применяется к замыканиям, которые не перемещают захваченные значения из своего тела и не изменяют захваченные значения, а также к замыканиям, которые ничего не захватывают из своего окружения. Эти замыкания можно вызывать более одного раза без изменения окружения, что важно в таких случаях, как одновременный вызов замыкания несколько раз.
    Давайте рассмотрим определение метода unwrap_or_else на
    Option
    , который мы использовали в листинге 13-1:
    Напомним, что
    T
    - это общий тип, отображающий тип значения в
    Some варианте
    Option
    . Этот тип
    T
    также является возвращаемым типом функции unwrap_or_else
    : код,
    вызывающий unwrap_or_else на
    Option
    , например, получит
    String
    Далее, обратите внимание, что функция unwrap_or_else имеет дополнительный параметр общего типа
    F
    . Тип
    F
    - это тип параметра f
    , который является замыканием,
    которое мы задаём при вызове unwrap_or_else
    Ограничение, заданное для общего типа
    F
    , - это
    FnOnce() -> T
    , что означает, что
    F
    должен вызываться один раз, не принимать аргументов и возвращать
    T
    . Использование
    FnOnce в ограничении трейта выражает ограничение того, что unwrap_or_else будет вызывать f
    не более одного раза. В теле unwrap_or_else мы видим, что если
    Option равно
    Some
    , то f
    вызываться не будет. Если
    Option будет равен
    None
    , f
    будет вызван один раз. Поскольку все замыкания реализуют
    FnOnce
    , unwrap_or_else принимает самые разные виды замыканий и является настолько гибким, насколько это возможно.
    Примечание: Функции также могут реализовывать все три признака
    Fn
    . Если то, что мы хотим сделать, не требует захвата значения из среды, мы можем использовать имя функции, а не замыкания, когда нам нужно что-то, реализующее один из признаков
    Fn
    . Например, для значения
    Option>
    мы можем вызвать impl

    Option
    { pub fn unwrap_or_else
    (
    self
    , f: F) -> T where
    F:
    FnOnce
    () -> T
    { match self
    {
    Some
    (x) => x,
    None
    => f(),
    }
    }
    }
    unwrap_or_else(Vec::new)
    , чтобы получить новый пустой вектор, если значение равно
    None
    Теперь рассмотрим метод стандартной библиотеки sort_by_key
    , определённый для срезов, чтобы увидеть, чем он отличается от unwrap_or_else и почему sort_by_key использует
    FnMut вместо
    FnOnce для ограничения трейта. Замыкание получает один аргумент в виде ссылки на текущий элемент в рассматриваемом срезе и возвращает значение типа
    K
    , которое может быть упорядочено. Эта функция полезна, когда вы хотите отсортировать срез по определённому атрибуту каждого элемента. В листинге 13-
    7 у нас есть список экземпляров
    Rectangle
    , и мы используем sort_by_key
    , чтобы упорядочить их по атрибуту width от меньшего к большему:
    Файл : src/main.rs
    Листинг 13-7: Использование
    sort_by_key
    для сортировки прямоугольников по ширине
    Этот код печатает:
    #[derive(Debug)]
    struct
    Rectangle
    { width: u32
    , height: u32
    ,
    } fn main
    () { let mut list = [
    Rectangle { width:
    10
    , height:
    1
    },
    Rectangle { width:
    3
    , height:
    5
    },
    Rectangle { width:
    7
    , height:
    12
    },
    ]; list.sort_by_key(|r| r.width); println!
    (
    "{:#?}"
    , list);
    }

    Причина, по которой sort_by_key определена как принимающая замыкание
    FnMut
    ,
    заключается в том, что она вызывает замыкание несколько раз: по одному разу для каждого элемента в срезе. Замыкание
    |r| r.width не захватывает, не изменяет и не перемещает ничего из своего окружения, поэтому оно удовлетворяет требованиям связанности признаков.
    И наоборот, в листинге 13-8 показан пример замыкания, которое реализует только признак
    FnOnce
    , потому что оно перемещает значение из среды. Компилятор не позволит нам использовать это замыкание с sort_by_key
    :
    Файл : src/main.rs
    $
    cargo run
    Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.41s
    Running `target/debug/rectangles`
    [
    Rectangle { width: 3, height: 5,
    },
    Rectangle { width: 7, height: 12,
    },
    Rectangle { width: 10, height: 1,
    },
    ]
    #[derive(Debug)]
    struct
    Rectangle
    { width: u32
    , height: u32
    ,
    } fn main
    () { let mut list = [
    Rectangle { width:
    10
    , height:
    1
    },
    Rectangle { width:
    3
    , height:
    5
    },
    Rectangle { width:
    7
    , height:
    12
    },
    ]; let mut sort_operations = vec!
    []; let value =
    String
    ::from(
    "by key called"
    ); list.sort_by_key(|r| { sort_operations.push(value); r.width
    }); println!
    (
    "{:#?}"
    , list);
    }

    Листинг 13-8: Попытка использовать замыкание
    FnOnce
    с
    sort_by_key
    Это надуманный, запутанный способ (который не работает) попытаться подсчитать количество вызовов sort_by_key при сортировке list
    . Этот код пытается выполнить подсчёт, перемещая value
    -
    String из окружения замыкания - в вектор sort_operations
    . Замыкание захватывает value
    , затем перемещает value из замыкания, передавая право собственности на value вектору sort_operations
    . Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает,
    потому что value уже не будет находиться в той среде, из которой его можно будет снова поместить в sort_operations
    ! Поэтому это замыкание реализует только
    FnOnce
    Когда мы пытаемся скомпилировать этот код, мы получаем ошибку, что value не может быть перемещено из замыкания, потому что замыкание должно реализовать
    FnMut
    :
    Ошибка указывает на строку в теле замыкания, которая перемещает value из окружения. Чтобы исправить это, нужно изменить тело замыкания так, чтобы оно не перемещало значения из окружения. Для подсчёта количества вызовов sort_by_key более простым способом является хранение счётчика в окружении и увеличение его значения в теле закрытия. Замыкание в листинге 13-9 работает с sort_by_key
    , поскольку оно фиксирует только изменяемую ссылку на счётчик num_sort_operations и поэтому может быть вызвано более одного раза:
    Файл : src/main.rs
    $
    cargo run
    Compiling rectangles v0.1.0 (file:///projects/rectangles) error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure
    -->
    src/main.rs:18:30
    |
    15 | let value = String::from("by key called");
    | ----- captured outer variable
    16 |
    17 | list.sort_by_key(|r| {
    | ______________________-
    18 | | sort_operations.push(value);
    | | ^^^^^ move occurs because `value` has type
    `String`, which does not implement the `Copy` trait
    19 | | r.width
    20 | | });
    | |_____- captured by this `FnMut` closure
    For more information about this error, try `rustc --explain E0507`. error: could not compile `rectangles` due to previous error

    Листинг 13-9: Использование замыкания
    FnMut
    с
    sort_by_key
    разрешено
    Трейты
    Fn важны при определении или использовании функций или типов,
    использующих замыкания. В следующем разделе мы обсудим итераторы. Многие методы итераторов принимают аргументы замыкания, поэтому не забывайте об этих деталях замыкания, по мере того как мы продвигаемся дальше!
    #[derive(Debug)]
    struct
    Rectangle
    { width: u32
    , height: u32
    ,
    } fn main
    () { let mut list = [
    Rectangle { width:
    10
    , height:
    1
    },
    Rectangle { width:
    3
    , height:
    5
    },
    Rectangle { width:
    7
    , height:
    12
    },
    ]; let mut num_sort_operations =
    0
    ; list.sort_by_key(|r| { num_sort_operations +=
    1
    ; r.width
    }); println!
    (
    "{:#?}, sorted in {num_sort_operations} operations"
    , list);
    }

    1   ...   30   31   32   33   34   35   36   37   ...   62


    написать администратору сайта