ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1170
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Листинг 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
;
Листинг 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();
}
Причина, по которой 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
замыкании
С добавлением аннотаций типов синтаксис замыканий выглядит более похожим на синтаксис функций. Здесь мы определяем функцию, которая добавляет 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
и 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-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(),
}
}
}
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
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);
}
, чтобы получить новый пустой вектор, если значение равно
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