@ -802,7 +802,7 @@ pool and think about how things would look different or the same with async.
@@ -802,7 +802,7 @@ pool and think about how things would look different or the same with async.
#### Creating a Finite Number of Threads
We want our thread pool to work in a similar, familiar way so that switching
from threads to a thread pool doesn’t require large changes to the code that
из основного потокаs to a thread pool doesn’t require large changes to the code that
uses our API. Listing 21-12 shows the hypothetical interface for a `ThreadPool`
Когда мы подключаем внешнее дополнение, Cargo берет последние исполнения всего, что нужно этого дополнения, из *перечня (registry)*, который является повтором данных с [Crates.io]. Crates.io — это место, где участники сообщества Ржавчины размещают свои дела с открытой исходной рукописью для использования другими.
После обновления перечня Cargo проверяет раздел `[dependencies]` и загружает все указанные в списке дополнения, которые ещё не были загружены. В нашем случае, хотя мы указали только `rand` в качестве дополнения, Cargo также захватил другие дополнения, от которых зависит работа `rand`. После загрузки всех дополнений Ржавчины происходит их сборка, а затем собирает дело с имеющимися дополнениями.
После обновления перечня Cargo проверяет раздел `[dependencies]` и загружает все указанные в списке дополнения, которые ещё не были загружены. В нашем случае, хотя мы указали только `rand` в качестве дополнения, Cargo также получил другие дополнения, от которых зависит работа `rand`. После загрузки всех дополнений Ржавчины происходит их сборка, а затем собирает дело с имеющимися дополнениями.
Если сразу же запустить `cargo build` снова, не внося никаких изменений, то кроме строки `Finished` вы не получите никакого вывода. Cargo знает, что он уже загрузил и собрал дополнения, и вы не вносили никаких изменений в файл *Cargo.toml*. Cargo также знает, что вы ничего не изменили в своей рукописи, поэтому он не пересоберет и его. Если делать нечего, он просто завершает работу.
<spanclass="caption">Приложение 3-4: Перебор каждой переменной собрания с помощью круговорота <code>while</code></span>
Эта рукопись использует перебор переменных массива. Он начинается с порядкового указателя `0`, а затем замкнуто выполняется, пока не достигнет последнего порядкового указателя в массиве (то есть, когда `index < 5` уже не является истиной). Выполнение этой рукописи выведет каждая переменная массива:
Эта рукопись использует перебор переменных массива. Он начинается с порядкового указателя `0`, а затем замкнуто выполняется, пока не достигнет последнего порядкового указателя в массиве (то есть, когда `index < 5` уже не является истиной). Итог выполнения рукописи выведет каждую переменную массива:
@ -171,7 +171,7 @@ don't want to include it for rustdoc testing purposes. -->
@@ -171,7 +171,7 @@ don't want to include it for rustdoc testing purposes. -->
Затем рукопись, вызывающая эту, будет обрабатывать полученное значение `Ok`, содержащего имя пользователя, либо значения `Err`, содержащего `io::Error`. Вызывающая рукопись должна решить, что делать с этими значениями. Если вызывающая рукопись получает значение `Err`, она может вызвать `panic!` и завершить работу программы, использовать имя пользователя по умолчанию или найти имя пользователя, например, не в файле. У нас недостаточно сведений о том, что на самом деле пытается сделать вызывающая рукопись, поэтому мы распространяем все сведения об успехах или ошибках вверх, чтобы она могла обрабатываться соответствующим образом.
Эта схема передачи ошибок настолько распространена в языке Ржавчина, что язык предоставляет приказчик вопросительного знака `?`, чтобы облегчить эту задачу.
Этот порядок передачи ошибок настолько распространена в языке Ржавчина, что язык предоставляет приказчик вопросительного знака `?`, чтобы облегчить эту задачу.
#### Сокращение для проброса ошибок: приказчик `?`
## Замыкания: безымянные функции, которые запечатлевают ("захватывают") своё окружение
## Замыкания: безымянные функции, которые запечатлевают ("получают") своё окружение
Замыкания в Ржавчине - это безымянные функции, которые можно сохранять в переменных или передавать в качестве переменных другим функциям. Вы можете создать замыкание в одном месте, а затем вызвать его в каком-нибудь другом, чтобы выполнить обработку в иной среде. В отличие от функций, замыкания могут использовать значения из области видимости в которой они были определены. Мы выполним, как эти функции замыканий открывают возможности для повторного использования рукописи и изменения её поведения.
### Захват переменных окружения с помощью замыкания
### Получение переменных окружения с помощью замыкания
Сначала мы рассмотрим, как с помощью замыканий можно использовать предметы из области, в которой они вместе были определены, для их последующего использования. Вот задумка: Время от времени наше предприятие по производству рубашек в качестве акции дарит рубашки ограниченного выпуска, выпущенные ограниченным тиражом, каким-нибудь пользователям из нашего списка рассылки. Люди из списка рассылки при желании могут выбрать любимый цвет в своём личном кабинете. Если человек, выбранный для получения бесплатной рубашки, указал свой любимый цвет, он получает рубашку этого цвета. Если человек не указал свой любимый цвет, он получит рубашку того цвета, которых у предприятия на данное мгновение больше всего.
Существует множество способов выполнить это. В данном примере мы будем использовать перечисление `ShirtColor`, которое может быть двух исходов`Red` и `Blue` (для простоты ограничим количество доступных цветов этими двумя). Запасы предприятия мы представим стопкой `Inventory`, которая состоит из поля `shirts`, содержащего `Vec<ShirtColor>`, в котором перечислены рубашки тех цветов, которые есть в наличии. Способ `giveaway`, определённый в `Inventory`, принимает необязательное свойство - цвет, предпочитаемый пользователем, выбранным для получения бесплатной рубашки, и возвращает тот цвет рубашки, который он получит в действительности. Эта схема показана в приложении 13-1:
Существует множество способов выполнить это. В данном примере мы будем использовать перечисление `ShirtColor`, которое имеет два исхода:`Red` и `Blue` (для простоты ограничим количество доступных цветов этими двумя). Запасы предприятия мы представим стопкой `Inventory`, которая состоит из поля `shirts`, содержащего `Vec<ShirtColor>`, в котором перечислены рубашки тех цветов, которые есть в наличии. Способ `giveaway`, определённый в `Inventory`, принимает необязательное свойство - цвет, предпочитаемый пользователем, выбранным для получения бесплатной рубашки, и возвращает тот цвет рубашки, который он получит в действительности. Этот порядок показана в приложении 13-1:
В магазине `store`, определённом в `main`, осталось две синие и одна красная рубашки для этой ограниченной акции. Мы вызываем способ `giveaway` для пользователя предпочитающего красную рубашку и для пользователя без каких-либо предпочтений.
Опять же, эта рукопись могла быть выполнена множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа `giveaway`, в котором используется замыкание. В способе `giveaway` мы получаем пользовательское предпочтение цвета как свойство вида данных `Option<ShirtColor>` и вызываем способ `unwrap_or_else` на`user_preference`. Способ <adata-md-type="raw_html"href="../std/option/enum.Option.html#method.unwrap_or_else">`unwrap_or_else` перечисления `Option<T>`</a><!-- ignore --> определён встроенной библиотекой. Он принимает одну переменную: замыкание без переменных, которое возвращает значение `T` (преобразуется в вид значения, которое окажется в исходе `Some` перечисления `Option<T>`, в нашем случае `ShirtColor`). Если `Option<T>` окажется исходом `Some`, `unwrap_or_else` вернёт значение из `Some`. А если `Option<T>` будет является исходом `None`, `unwrap_or_else` вызовет замыкание и вернёт значение, возвращённое замыканием.
Опять же, эта рукопись могла быть выполнена множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа `giveaway`, в котором используется замыкание. В способе `giveaway` мы получаем пользовательское предпочтение цвета как свойство вида данных `Option<ShirtColor>` и применяем способ `unwrap_or_else` для`user_preference`. Способ <adata-md-type="raw_html"href="../std/option/enum.Option.html#method.unwrap_or_else">`unwrap_or_else` перечисления `Option<T>`</a><!-- ignore --> определён встроенной библиотекой. Он принимает одну переменную: значение `T` (возвращается значение в заранее заданном виде, которое окажется в исходе `Some` перечисления `Option<T>`, в нашем случае `ShirtColor`). Если `Option<T>` окажется исходом `Some`, `unwrap_or_else` вернёт значение из `Some`. А если `Option<T>` будет является исходом `None`, `unwrap_or_else` вызовет замыкание и вернёт значение, полученное из замыкания.
В качестве переменной `unwrap_or_else` мы передаём замыкание `|| self.most_stocked()`. Это замыкание, которое не принимает никаких свойств (если бы у замыкания были свойства, они были бы перечислены между двумя вертикальными полосами). В теле замыкания вызывается `self.most_stocked()`. Здесь мы определили замыкание, а использование `unwrap_or_else` такова, что выполнится оно позднее, когда потребуется получить итог.
В качестве переменной `unwrap_or_else` мы передаём замыкание `|| self.most_stocked()`. Это замыкание, которое не принимает никаких свойств (если бы у замыкания были свойства, они были бы перечислены между двумя вертикальными полосами). В теле замыкания вызывается `self.most_stocked()`. Здесь мы определили замыкание, а использование `unwrap_or_else` таково, что оно выполнится позднее, когда потребуется получить итог.
Важной особенностью здесь является то, что мы передали замыкание, которое вызывает `self.most_stocked()` текущего образца данных`Inventory`. Обычной библиотеке не нужно знать ничего о видах `Inventory` или `ShirtColor`, которые мы определили, или о ходу мыслей, которую мы хотим использовать в этом задумки. Замыкание определяет неизменяемую ссылку на `self``Inventory` и передаёт её с указанным нами рукописью в способ `unwrap_or_else`. А вот функции не могут определять своё окружение таким образом.
Важной особенностью здесь является то, что мы вызвали замыкание, которое в свою очередь вызывает `self.most_stocked()` - способ для нашего образца данных (стопки)`Inventory`. Обычной библиотеке не нужно знать ничего о видах данных: стопка -> `Inventory` или перечисление -> `ShirtColor`, которые мы определили, или о ходе мыслей, который мы хотим использовать для решения данной задачи. Замыкание определяет неизменяемую ссылку на `self``Inventory` и передаёт её с указанным нами содержимым в способ `unwrap_or_else`. А вот функции не могут определять своё окружение таким образом.
### Выведение и изложение видов замыкания
Есть и другие различия между функциями и замыканиями. Замыкания обычно не требуют определения видов данных в входных свойств или возвращаемого значения, как это делается в функциях `fn`. Изложения видов данных требуются для функций, потому что виды данных являются частью явного внешней оболочки, предоставляемого пользователям. Жёсткое определение таких внешних оболочек важно для того, чтобы все были согласованы в том, какие виды значений использует и возвращает функция. А вот замыкания, напротив, не употребляются в значении подобных открытых внешних оболочек: они хранятся в переменных, используются не имея имени и незримо для пользователей нашей библиотеки.
Есть и другие различия между функциями и замыканиями. Замыкания обычно не требуют определения видов данных в входных свойств или возвращаемого значения, как это делается в функциях `fn`. Изложения видов данных требуются для функций, потому что виды данных находятся за пределами самой функции, предоставляемого пользователям. Жёсткое определение видов данных необходимо для того, чтобы все вида данных были согласованы в том, какие виды данных использует и возвращает функция. А вот замыкания, напротив, для них не требуется строго указывать виды данных: так как они могут храниться в переменных и использоваться не имея имени, при этом незримо для пользователей нашей библиотеки.
Замыкания, как правило, небольшие и уместны в каком-то узконаправленном среде, а не в произвольных случаях. В этих ограниченных средах сборщик может вывести виды свойств и возвращаемого вида данных, подобно тому, как он может вывести виды данных большинства переменных (есть редкие случаи, когда сборщику также нужны изложении видов замыканий).
Замыкания, как правило, небольшие и уместны в какой-то узконаправленном среде, а не в произвольных случаях. В этих ограниченных средах сборщик может вывести виды свойств и возвращаемого вида данных, подобно тому, как он может вывести виды данных большинства переменных (есть редкие случаи, когда сборщику также нужны изложения видов замыканий).
Как и в случае с переменными, мы можем добавить изложении видов данных, если хотим повысить ясность и чёткость описания ценой увеличения многословности, большей чем это необходимо. Определение видов данных для замыкания будет выглядеть как определение, показанное в приложении 13-2. В этом примере мы определяем замыкание и храним его в переменной, а не определяем замыкание в том месте, куда мы передаём его в качестве переменной, как это было в приложении 13-1.
Как и в случае с переменными, мы можем добавить изложения видов данных, если хотим внести определенность при описании за счёт увеличения рукописи. Определение видов данных для замыкания будет выглядеть как определение в приложении 13-2. В этом примере в переменной определено замыкание, при этом мы не определяем замыкание в том месте, куда мы передаём его в качестве переменной, как это было в приложении 13-1.
<spanclass="caption">Приложение 13-2: Добавление необязательных наставлений видов свойств и возвращаемых значений в замыкании</span>
С добавлением наставлений видов данных правила написания замыканий выглядит более похожим на правила написания функций. Здесь мы, для сравнения, определяем функцию, которая добавляет 1 к своему свойству, и замыкание, которое имеет такое же поведение. Мы добавили несколько пробелов, чтобы выровнять соответствующие части. Это показывает, что правила написания замыкания похож на правила написания функции, за исключением использования труб (вертикальная черта) и количества необязательного правил написания:
С добавлением описаний видов данных, правила написания замыканий выглядят более похожими на правила написания функций. Здесь мы для сравнения, определяем функцию, которая добавляет 1 к своему значению, и замыкание, которое имеет такое же поведение. Мы добавили несколько пробелов, чтобы выровнять соответствующие части. Это показывает, что правила написания замыкания похожи на правила написания функции, за исключением использования труб (вертикальная черта) и дополнительного количества необязательных пояснений согласно правилам написания:
```rust,ignore
fn add_one_v1 (x: u32) -> u32 { x + 1 }
@ -65,9 +65,9 @@ let add_one_v3 = |x| { x + 1 };
@@ -65,9 +65,9 @@ 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` необходимо вставить либо изложении видов данных, либо значения некоторого вида данных, чтобы Ржавчина смогла вывести вид данных.
В первой строке описано определение функции, а во второй - полностью расписано всё замыкание от начала и до конца. В третьей строке мы удаляем из изложения описания видов данных при описании замыкания. В четвёртой строке мы убираем узорчатые скобки, которые являются необязательными, поскольку тело замыкания содержит только одно действие. Все выше указанные изложения являются правильными согласно правил написания и являются равноценными с точки зрения поведения при их вызове. Строки `add_one_v3` и `add_one_v4` требуют, чтобы виды данных, используемых в замыканиях были заранее определены до сборки, поскольку виды данных не определены. Это похоже на `let v = Vec::new();`, когда для `Vec` необходимо указать вид данных, либо значения ранее определенного вида данных, чтобы Ржавчина смогла понять с каким видом данных ей предстоит выполнять действия.
Для определений замыкания сборщик выводит определенные виды данных для каждого из свойств и возвращаемого значения. Например, в приложении 13-3 показано определение короткого замыкания, которое просто возвращает значение, полученное в качестве свойства. Это замыкание не очень полезно, кроме как для целей данного примера. Обратите внимание, что мы не добавили в определение никаких наставлений видо данных. Поскольку наставлений видов данных нет, мы можем вызвать замыкание для любого вида данных, что мы и сделали в первый раз с `String`. Если затем мы попытаемся вызвать `example_closure` для целого числа, мы получим ошибку.
Для определения итога замыкания сборщик определяет виды данных для каждого из входных и возвращаемых значений. Например, в приложении 13-3 показано определение короткого замыкания, которое просто возвращает значение, полученное в качестве входного. Это замыкание бесполезно, показано для примера. Обратите внимание, что мы не указали в описании вид данных. Поскольку виды данных не определены, мы можем вызвать замыкание для любого вида данных, что мы и сделали в первый раз с `String`. Но если мы затем попытаемся вызвать `example_closure` для другого вида данных - например целого числа, мы получим ошибку.
При первом вызове `example_closure` со значением `String` сборщик определяет вид данных `x` и возвращаемый вид данных замыкания как `String`. Эти виды данных затем определятся в замыкании в `example_closure`, и мы получаем ошибку вида данных при следующей попытке использовать другой вид данных с тем же замыканием.
При первом вызове `example_closure` со значением `String` сборщик определяет входной (`x`) и выходной вид данных для данного замыкания как `String`. Этот вид данных затем привязывается к замыканию в `example_closure`, и мы получаем ошибку из-за недопустимого вида данных при следующей попытке , так как нельзя повторно использовать другой вид данных с тем же замыканием.
### Захват ссылок или передача владения
### Получение ссылок или передача владения
Замыкания могут захватывать значения из своего окружения тремя способами, которые соответствуют тем же трём способам, которыми функция может принимать свойства: заимствование неизменяемых, заимствование изменяемых и получение владения. Замыкание самостоятельно определяет, какой из этих способов использовать, исходя из того, что тело функции делает с полученными значениями.
Замыкания могут получать значения из своего окружения тремя способами, которые соответствуют тем же трём способам, которыми функция может принимать свойства: заимствование неизменяемое, заимствование изменяемое и получение владения. Замыкание самостоятельно определяет, какой из этих способов использовать, исходя из того, что тело замыкания делает с полученными значениями.
В приложении 13-4 мы определяем замыкание, которое захватывает неизменяемую ссылку на вектор с именем `list`, поскольку неизменяемой ссылки достаточно для вывода значения:
В приложении 13-4 мы определяем замыкание, которое получает неизменяемую ссылку на вектор с именем `list`, поскольку неизменяемой ссылки достаточно для вывода значения:
<spanclass="caption">Приложение 13-4: Определение и вызов замыкания, которое захватывает неизменяемую ссылку</span>
<spanclass="caption">Приложение 13-4: Определение и вызов замыкания, которое получает неизменяемую ссылку</span>
Этот пример также отображает, то что переменная может быть привязана к определению замыкания, и в дальнейшем мы можем вызвать замыкание, используя имя переменной и круглые скобки, как если бы имя переменной было именем функции.
Этот пример также отображает, то что переменная может быть привязана к вызову самого замыкания, и в дальнейшем мы можем вызвать замыкание, используя имя переменной и круглые скобки, как если бы имя переменной было именем функции.
Поскольку мы можем иметь несколько неизменяемых ссылок на `list` одновременно, `list` остаётся доступным из рукописи до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Эта рукопись собирается, выполняется и выводит:
Поскольку мы можем иметь несколько неизменяемых ссылок на `list` одновременно, `list` остаётся доступным из рукописи до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Итог данной рукописи:
В следующем приложении 13-5 мы изменили тело замыкания так, чтобы оно добавляло переменную в вектор `list`. Теперь замыкание захватывает изменяемую ссылку:
В следующем приложении 13-5 мы изменили тело замыкания так, чтобы оно добавляло переменную в вектор `list`. Теперь замыкание получает изменяемую ссылку:
Обратите внимание, что между определением и вызовом замыкания `borrows_mutably` больше нет `println!`: когда определяется `borrows_mutably`, оно захватывает изменяемую ссылку на `list`. После вызова замыкания мы больше не используем его, поэтому изменяемое заимствование заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для вывода недоступно, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы. Попробуйте добавить туда `println!` и посмотрите, какое сообщение об ошибке вы получите!
Обратите внимание, что между определением и вызовом замыкания `borrows_mutably` больше нет `println!`: когда определяется `borrows_mutably`, оно получает изменяемую ссылку на `list`. После вызова замыкания мы больше не используем его, поэтому изменяемое заимствование заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для вывода недоступно, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы. Попробуйте добавить туда `println!` и посмотрите, какое сообщение об ошибке вы получите!
Если вы хотите заставить замыкание принять владение значениями, которые оно использует в окружении, даже если в теле замыкания нет рукописи, требующего владения, вы можете использовать ключевое слово `move` перед списком свойств.
Если вы хотите заставить замыкание принять владение значениями, которые оно использует в окружении, даже если в теле замыкания нет рукописи, требующей владения, вы можете использовать ключевое слово `move` перед указанием свойств.
Это средства в основном полезно при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово `move`. В приложении 13-6 показана рукопись из приложения 13-4, измененный для вывода вектора в новом потоке, а не в основном потоке:
Это средство в основном полезно при передаче замыкания владения новому потоку, чтобы переместить владение данными так, чтобы ими владел новый поток. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности и многопоточности, а пока давайте вкратце рассмотрим порождение порождённого потока с помощью замыкания, в котором используется ключевое слово `move`. В приложении 13-6 показана рукопись из приложения 13-4, измененная для вывода вектора в новом потоке, а не в основном:
<spanclass="filename">Файл: src/main.rs</span>
@ -137,7 +137,7 @@ let add_one_v4 = |x| x + 1 ;
@@ -137,7 +137,7 @@ let add_one_v4 = |x| x + 1 ;
<spanclass="caption">Приложение 13-6: Использование <code>move</code> для принуждения замыкания потока принять на себя владение <code>list</code></span>
Мы порождаем новый поток, передавая ему в качестве переменной замыкание для выполнения. Тело замыкания выводит список. В приложении 13-4 замыкание захватило `list` только с помощью неизменяемой ссылки, потому что это наименьше необходимый доступ к `list` для его выводе. В этом примере, несмотря на то, что тело замыкания по-прежнему требует только неизменяемой ссылки, нам нужно указать, что `list` должен быть перемещён в замыкание, поместив ключевое слово `move` в начало определения замыкания. Новый поток может завершиться раньше, чем завершится основной поток, или основной поток может завершиться первым. Если основной поток сохранил владение `list`, но завершился раньше нового потока и удалил `list`, то неизменяемая ссылка в потоке будет недействительной. Поэтому сборщик требует, чтобы `list` был перемещён в замыкание, переданное новому потоку, чтобы ссылка была действительной. Попробуйте убрать ключевое слово `move` или использовать `list` в основном потоке после определения замыкания и посмотрите, какие ошибки сборщика вы получите!
Мы порождаем новый поток, передавая ему в качестве переменной замыкание для выполнения. Тело замыкания выводит список. В приложении 13-4 замыкание получило `list` только с помощью неизменяемой ссылки, потому что это наименьший уровень доступа, необходимый для обращения к `list` для вывода. В этом примере, несмотря на то, что тело замыкания по-прежнему требует только неизменяемой ссылки, нам нужно указать, что `list` должен быть передан во владение замыканию, поместив ключевое слово `move` в начало определения замыкания. Порожденный поток может завершиться раньше, чем завершится основной поток, или основной поток может завершиться первым. Если основной поток сохранил владение `list`, но завершился раньше порождённого потока и удалил `list`, то неизменяемая ссылка в потоке будет недействительной. Поэтому сборщик требует, чтобы `list` был передан во владение замыканию, которое передаёт владение порожденному потоку, чтобы ссылка была действительной. Попробуйте убрать ключевое слово `move` или использовать `list` в основном потоке после вызова замыкания и посмотрите, какие ошибки сборщика вы получите!
<!-- Old headings. Do not remove or links may break. -->
@ -145,15 +145,15 @@ let add_one_v4 = |x| x + 1 ;
@@ -145,15 +145,15 @@ let add_one_v4 = |x| x + 1 ;
### Перемещение захваченных значений из замыканий и сущности `Fn`
### Перемещение полученных значений из замыканий и сущности `Fn`
После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается *в* замыкание), рукопись в теле замыкания определяет, что происходит со ссылками или значениями, в мгновение последующего выполнения замыкания (тем самым влияя на то, что перемещается *из* замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды.
После того, как замыкание получило ссылку или право владения значением из области видимости, в которой оно определено (тем самым влияя на то, что перемещается *в* замыкание), рукопись в теле замыкания определяет, что происходит со ссылками или значениями, во время последующего выполнения (тем самым влияя на то, что перемещается *из* замыкания). Тело замыкания может делать любое из следующих действий: перемещать полученное значение из замыкания, изменять полученное значение, не перемещать и не изменять значение или вообще ничего не получать из среды.
То, как замыкание получает и обрабатывает значения из своего окружения, указывает на то, какие сущности используют замыкание, а с помощью сущностей функции и стопки могут определять, какие виды замыканий они могут использовать. Замыканиям самостоятельно присваивается выполнение одного, двух или всех трёх из нижеперечисленных сущностей `Fn`, аддитивным образом, в зависимости от того, как тело замыкания обрабатывает значения:
1. `FnOnce` применяется к замыканиям, которые могут быть вызваны один раз. Все замыкания используют по крайней мере эту сущность, потому что все замыкания могут быть вызваны. Замыкание, которое перемещает захваченные значения из своего тела, использует только `FnOnce` и ни один из других признаков `Fn`, потому что оно может быть вызвано только один раз.
2. `FnMut` применяется к замыканиям, которые не перемещают захваченные значения из своего тела, но могут изменять захваченные значения. Такие замыкания могут вызываться более одного раза.
3. `Fn` применяется к замыканиям, которые не перемещают захваченные значения из своего тела и не изменяют захваченные значения, а также к замыканиям, которые ничего не захватывают из своего окружения. Такие замыкания могут выполняться более одного раза и не меняют ничего в своём окружении, что важно в таких случаях, как одновременный вызов замыкания несколько раз.
1. `FnOnce` применяется к замыканиям, которые могут быть вызваны один раз. Все замыкания используют по крайней мере эту сущность, потому что все замыкания могут быть вызваны. Замыкание, которое передаёт право владения на полученные значения из своего тела, использует только `FnOnce` и ни один из других признаков `Fn`, потому что оно может быть вызвано только один раз.
2. `FnMut` применяется к замыканиям, которые не передают полученные значения из своего тела, но могут изменять полученные значения. Такие замыкания могут вызываться более одного раза.
3. `Fn` применяется к замыканиям, которые не передают право владения на полученные значения из своего тела и не изменяют полученные значения, а также к замыканиям, которые ничего не получают из своего окружения. Такие замыкания могут выполняться более одного раза и не меняют ничего в своём окружении, что важно в таких случаях, как одновременный вызов замыкания несколько раз.
Давайте рассмотрим определение способа `unwrap_or_else` у `Option<T>`, который мы использовали в приложении 13-1:
Ограничением сущности, заданным для обобщённого вида данных `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<Vec<T>>` мы можем вызвать `unwrap_or_else(Vec::new)`, чтобы получить новый пустой вектор, если значение окажется `None`.
> Примечание: Функции также могут использовать все три сущности `Fn`. Если то, что мы хотим сделать, не требует получения значения из среды, мы можем передавать имя какой-либо функции, а не замыкания, когда нам нужно что-то, выполняющее одну из сущностей `Fn`. Например, для значения `Option<Vec<T>>` мы можем вызвать `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` от меньшего к большему:
Причина, по которой `sort_by_key` определена как принимающая замыкание `FnMut`, заключается в том, что она вызывает замыкание несколько раз: по одному разу для каждой переменной в срезе. Замыкание `|r| r.width` не захватывает, не изменяет и не перемещает ничего из своего окружения, поэтому оно удовлетворяет требованиям связанности признаков.
Причина, по которой `sort_by_key` определена как принимающая замыкание `FnMut`, заключается в том, что она вызывает замыкание несколько раз: по одному разу для каждой переменной в срезе. Замыкание `|r| r.width` не получает, не изменяет и не перемещает ничего из своего окружения, поэтому оно удовлетворяет требованиям связанности признаков.
И наоборот, в приложении 13-8 показан пример замыкания, которое использует только признак `FnOnce`, потому что оно перемещает значение из среды. Сборщик не позволит нам использовать это замыкание с `sort_by_key`:
<spanclass="caption">Приложение 13-8: Попытка использовать замыкание <code>FnOnce</code> с <code>sort_by_key</code></span>
Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов `sort_by_key` при упорядочиванию `list`. Эта рукопись пытается выполнить подсчёт, перемещая `value` - `String` из окружения замыкания - в вектор `sort_operations`. Замыкание захватывает `value`, затем перемещает `value` из замыкания, передавая владение на `value` вектору `sort_operations`. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что `value` уже не будет находиться в той среде, из которой его можно будет снова поместить в `sort_operations`! Поэтому это замыкание использует только `FnOnce`. Когда мы попытаемся собрать эту рукопись, мы получим ошибку сообщающую о том что `value` не может быть перемещено из замыкания, потому что замыкание должно использовать `FnMut`:
Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов `sort_by_key` при упорядочиванию `list`. Эта рукопись пытается выполнить подсчёт, перемещая `value` - `String` из окружения замыкания - в вектор `sort_operations`. Замыкание получает `value`, затем перемещает `value` из замыкания, передавая владение на `value` вектору `sort_operations`. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что `value` уже не будет находиться в той среде, из которой его можно будет снова поместить в `sort_operations`! Поэтому это замыкание использует только `FnOnce`. Когда мы попытаемся собрать эту рукопись, мы получим ошибку сообщающую о том что `value` не может быть перемещено из замыкания, потому что замыкание должно использовать `FnMut`:
Вы можете выстроить цепочку из нескольких вызовов переходников повторителя для выполнения сложных действий удобном для прочтения виде. Но поскольку все повторители являются "ленивыми", для получения итогов вызовов переходников повторителя необходимо вызвать один из способов потребляющего переходника.
### Использование замыканий, которые захватывают переменные окружения
### Использование замыканий, которые получают переменные окружения
Многие переходники повторителей принимают замыкания в качестве переменных, и обычно замыкания, которые мы будем указывать в качестве переменных переходникам повторителей, это замыкания, которые определяют (захватывают) своё окружение.
Многие переходники повторителей принимают замыкания в качестве переменных, и обычно замыкания, которые мы будем указывать в качестве переменных переходникам повторителей, это замыкания, которые определяют (получают) своё окружение.
В этом примере мы будем использовать способ `filter`, который принимает замыкание. Замыкание получает переменную из повторителя и возвращает `bool`. Если замыкание возвращает `true`, значение будет включено в повторение, создаваемую `filter`. Если замыкание возвращает `false`, значение не будет включено.
В приложении 13-16 мы используем `filter` с замыканием, которое захватывает переменную `shoe_size` из своего окружения для повторения по собрания образцов стопки `Shoe`. Он будет возвращать обувь только указанного размера.
В приложении 13-16 мы используем `filter` с замыканием, которое получает переменную `shoe_size` из своего окружения для повторения по собрания образцов стопки `Shoe`. Он будет возвращать обувь только указанного размера.
В теле `shoes_in_my_size` мы вызываем `into_iter` чтобы создать повторитель , который становится владельцем вектора. Затем мы вызываем `filter`, чтобы превратить этот повторитель в другой, который содержит только переменные, для которых замыкание возвращает `true`.
Замыкание захватывает свойство `shoe_size` из окружения и сравнивает его с размером каждой пары обуви, оставляя только обувь указанного размера. Наконец, вызов `collect` собирает значения, возвращаемые приспособленным повторителем, в вектор, возвращаемый функцией.
Замыкание получает свойство `shoe_size` из окружения и сравнивает его с размером каждой пары обуви, оставляя только обувь указанного размера. Наконец, вызов `collect` собирает значения, возвращаемые приспособленным повторителем, в вектор, возвращаемый функцией.
Проверка показывает, что когда мы вызываем `shoes_in_my_size`, мы возвращаем только туфли, размер которых совпадает с указанным нами значением.
Это средства довольно изящно! Используя `RefCell<T>`, мы получаем внешне неизменяемое значение `List`. Но мы можем использовать способы `RefCell<T>`, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших стопок данных. Обратите внимание, что `RefCell<T>` не работает для многопоточного рукописи! `Mutex<T>` - это thread-safe исполнение `RefCell<T>`, а `Mutex<T>` мы обсудим в главе 16.
Это средство довольно изящно! Используя `RefCell<T>`, мы получаем внешне неизменяемое значение `List`. Но мы можем использовать способы `RefCell<T>`, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших стопок данных. Обратите внимание, что `RefCell<T>` не работает для многопоточного рукописи! `Mutex<T>` - это thread-safe исполнение `RefCell<T>`, а `Mutex<T>` мы обсудим в главе 16.
["Где находится приказчик `->`?"]: ch05-03-method-syntax.html#wheres-the---operator
@ -118,9 +118,9 @@ changes in the compiler -->
@@ -118,9 +118,9 @@ changes in the compiler -->
### Использование `move`-замыканий в потоках
Мы часто используем ключевое слово `move` с замыканиями, переданными в `thread::spawn`, потому что в этом случае замыкание получает из окружения права владения на используемые им значения, таким образом передавая права владения этими значениями от одного потока к другому. В разделе ["Захват ссылок или перемещение прав владения"] Главы 13 мы обсудили `move` в среде замыканий. Теперь мы сосредоточимся на взаимодействии между `move` и `thread::spawn`.
Мы часто используем ключевое слово `move` с замыканиями, переданными в `thread::spawn`, потому что в этом случае замыкание получает из окружения права владения на используемые им значения, таким образом передавая права владения этими значениями от одного потока к другому. В разделе ["Получение ссылок или перемещение прав владения"] Главы 13 мы обсудили `move` в среде замыканий. Теперь мы сосредоточимся на взаимодействии между `move` и `thread::spawn`.
Обратите внимание, что в приложении 16-1 замыкание, которое мы передаём в `thread::spawn` не принимает переменных: мы не используем никаких данных из основного потока в рукописи порождённого потока. Чтобы использовать данные из основного потока в порождённом потоке, замыкание порождённого потока должно захватывать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать вектор в главном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение.
Обратите внимание, что в приложении 16-1 замыкание, которое мы передаём в `thread::spawn` не принимает переменных: мы не используем никаких данных из основного потока в рукописи порождённого потока. Чтобы использовать данные из основного потока в порождённом потоке, замыкание порождённого потока должно получать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать вектор в главном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение.
<spanclass="filename">Файл: src/main.rs</span>
@ -130,13 +130,13 @@ changes in the compiler -->
@@ -130,13 +130,13 @@ changes in the compiler -->
<spanclass="caption">Приложение 16-3: Попытка использовать вектор, созданный основным потоком, в другом потоке</span>
Замыкание использует переменную `v`, поэтому оно захватит `v` и сделает его частью окружения замыкания. Поскольку `thread::spawn` запускает это замыкание в новом потоке, мы должны иметь доступ к `v` внутри этого нового потока. Но при сборке этого примера, мы получаем следующую ошибку:
Замыкание использует переменную `v`, поэтому оно получит `v` и сделает его частью окружения замыкания. Поскольку `thread::spawn` запускает это замыкание в новом потоке, мы должны иметь доступ к `v` внутри этого нового потока. Но при сборке этого примера, мы получаем следующую ошибку:
Ржавчина *выводит* как захватить `v` и так как в `println!` нужна только ссылка на `v`, то замыкание пытается заимствовать `v`. Однако есть неполадка: Ржавчина не может определить, как долго будет работать порождённый поток, поэтому он не знает, будет ли всегда действительной ссылка на `v`.
Ржавчина *выводит* как получить `v` и так как в `println!` нужна только ссылка на `v`, то замыкание пытается заимствовать `v`. Однако есть неполадка: Ржавчина не может определить, как долго будет работать порождённый поток, поэтому он не знает, будет ли всегда действительной ссылка на `v`.
В приложении 16-4 приведён задумка, который с большей вероятностью будет иметь ссылку на `v`, что будет недопустимо:
@ -146,7 +146,7 @@ changes in the compiler -->
@@ -146,7 +146,7 @@ changes in the compiler -->
<spanclass="caption">Приложение 16-4. Поток с замыканием, который пытается захватить ссылку на <code>v</code> из основного потока, удаляющего <code>v</code></span>
<spanclass="caption">Приложение 16-4. Поток с замыканием, который пытается получить ссылку на <code>v</code> из основного потока, удаляющего <code>v</code></span>
Если бы Ржавчина позволил нам запустить эту рукопись, есть вероятность, что порождённый поток был бы немедленно переведён в фоновый режим, не выполнив ничего. Порождённый поток имеет ссылку на `v`, но основной поток немедленно удаляет `v` , используя функцию `drop` , которую мы обсуждали в главе 15. Затем, когда порождённый поток начинает выполняться, `v` уже не существует, поэтому ссылка на него также будет недействительной. О, нет!
@ -184,4 +184,4 @@ help: to force the closure to take ownership of `v` (and any other referenced va
@@ -184,4 +184,4 @@ help: to force the closure to take ownership of `v` (and any other referenced va
Имея достаточное понимание потоков и API потоков, давайте посмотрим, что мы можем *делать* с помощью потоков.
["Захват ссылок или перемещение прав владения"]: ch13-01-closures.html#capturing-references-or-moving-ownership
["Получение ссылок или перемещение прав владения"]: ch13-01-closures.html#capturing-references-or-moving-ownership
<spanclass="caption">Приложение 18-29: Использование <code>@</code> для привязывания значения в образце, с одновременной его проверкой</span>
В этом примере будет выведено `Found an id in range: 5`. Указывая `id_variable @` перед рядом `3..=7`, мы захватываем любое значение, попадающее в ряд, одновременно проверяя, что это значение соответствует ряду в образце.
В этом примере будет выведено `Found an id in range: 5`. Указывая `id_variable @` перед рядом `3..=7`, мы получаем любое значение, попадающее в ряд, одновременно проверяя, что это значение соответствует ряду в образце.
Во второй ветке, где у нас в образце указан только ряд, рукопись этой ветки не имеет переменной, которая содержит действительное значение поля `id`. Значение поля `id` могло бы быть 10, 11 или 12, но рукопись, соответствующую этому образцу данных, не знает, чему оно равно. Рукопись образца не может использовать значение из поля `id`, потому что мы не сохранили значение `id` в переменной.
Указание имени сущности перед именем способа проясняет сборщику Ржавчина, какую именно использование `fly` мы хотим вызвать. Мы могли бы также написать `Human::fly(&person)`, что равнозначно используемому нами `person.fly()` в приложении 19-18, но это писание немного длиннее, когда нужна неоднозначность.
Теперь мы ошибка возникает из-за того, что у нас нет способа `execute` в стопке `ThreadPool`. Вспомните раздел ["Создание конечного числа потоков"](#creating-a-finite-number-of-threads)<!-- ignore -->, в котором мы решили, что наш объединение потоков должно иметь внешнюю оболочку, похожий на `thread::spawn`. Кроме того, мы выполняем функцию `execute`, чтобы она принимала замыкание и передавала его свободному потоку из объединения для запуска.
Мы определим способ `execute` у `ThreadPool`, принимающий замыкание в качестве свойства. Вспомните из раздела ["Перемещение захваченных значений из замыканий и сущности `Fn`"](ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits) <!-- ignore --> Главы 13 сведения о том, что мы можем принимать замыкания в качестве свойств тремя различными сущностями: `Fn` , `FnMut` и `FnOnce`. Нам нужно решить, какой вид данных замыкания использовать здесь. Мы знаем, что в конечном счёте мы сделаем что-то похожее на выполнение встроенной библиотеки `thread::spawn`, поэтому мы можем посмотреть, какие ограничения накладывает на свой свойство ярлык функции `thread::spawn`. Пособие показывает следующее:
Мы определим способ `execute` у `ThreadPool`, принимающий замыкание в качестве свойства. Вспомните из раздела ["Перемещение полученных значений из замыканий и сущности `Fn`"](ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits) <!-- ignore --> Главы 13 сведения о том, что мы можем принимать замыкания в качестве свойств тремя различными сущностями: `Fn` , `FnMut` и `FnOnce`. Нам нужно решить, какой вид данных замыкания использовать здесь. Мы знаем, что в конечном счёте мы сделаем что-то похожее на выполнение встроенной библиотеки `thread::spawn`, поэтому мы можем посмотреть, какие ограничения накладывает на свой свойство ярлык функции `thread::spawn`. Пособие показывает следующее:
<spanclass="caption">Приложение 20-20: Получение и выполнение заданий в потоке "работника"</span>
Здесь мы сначала вызываем `lock` у `receiver`, чтобы получить взаимное исключение, а затем вызываем `unwrap`, чтобы со сбоем завершить работу при любых ошибках. Захват запрета может завершиться неудачей, если взаимное исключение находится в *отравленном* состоянии (poisoned state), что может произойти, если какой-то другой поток завершился со сбоем, удерживая запрет, вместо снятия запрета. В этой случае вызвать `unwrap` для со сбоем завершения потока вполне оправдано. Не стесняйтесь заменить `unwrap` на `expect` с сообщением об ошибке, которое имеет для вас значение.
Здесь мы сначала вызываем `lock` у `receiver`, чтобы получить взаимное исключение, а затем вызываем `unwrap`, чтобы со сбоем завершить работу при любых ошибках. Получение запрета может завершиться неудачей, если взаимное исключение находится в *отравленном* состоянии (poisoned state), что может произойти, если какой-то другой поток завершился со сбоем, удерживая запрет, вместо снятия запрета. В этой случае вызвать `unwrap` для со сбоем завершения потока вполне оправдано. Не стесняйтесь заменить `unwrap` на `expect` с сообщением об ошибке, которое имеет для вас значение.
Если мы получили запрет взаимного исключения, мы вызываем `recv`, чтобы получить `Job` из потока. Последний вызов `unwrap` позволяет миновать любые ошибки, которые могут возникнуть, если поток, управляющий отправитель, прекратил исполняться, подобно тому, как способ `send` возвращает `Err`, если получатель не принимает сообщение.