소개
클로저: 환경을 캡처하는 익명 함수에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 Rust 의 클로저를 탐구합니다. 클로저는 변수에 저장하거나 인수로 전달할 수 있는 익명 함수로, 정의된 범위에서 값을 캡처하여 코드 재사용과 동작 사용자 지정을 가능하게 합니다.
클로저: 환경을 캡처하는 익명 함수에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 Rust 의 클로저를 탐구합니다. 클로저는 변수에 저장하거나 인수로 전달할 수 있는 익명 함수로, 정의된 범위에서 값을 캡처하여 코드 재사용과 동작 사용자 지정을 가능하게 합니다.
Rust 의 클로저는 변수에 저장하거나 다른 함수에 인수로 전달할 수 있는 익명 함수입니다. 클로저는 한 곳에서 생성한 다음 다른 곳에서 호출하여 다른 컨텍스트에서 평가할 수 있습니다. 함수와 달리 클로저는 정의된 범위에서 값을 캡처할 수 있습니다. 이러한 클로저 기능이 코드 재사용과 동작 사용자 지정을 어떻게 가능하게 하는지 보여드리겠습니다.
먼저 클로저를 사용하여 정의된 환경에서 값을 캡처하여 나중에 사용하는 방법을 살펴보겠습니다. 시나리오는 다음과 같습니다. 우리 티셔츠 회사는 프로모션으로 메일링 리스트에 있는 사람에게 독점적인 한정판 셔츠를 가끔씩 증정합니다. 메일링 리스트에 있는 사람들은 프로필에 자신이 좋아하는 색상을 선택적으로 추가할 수 있습니다. 무료 셔츠를 받을 사람의 선호하는 색상이 설정되어 있으면 해당 색상의 셔츠를 받습니다. 선호하는 색상을 지정하지 않은 사람은 회사에서 현재 가장 많이 보유하고 있는 색상의 셔츠를 받습니다.
이것을 구현하는 방법은 여러 가지가 있습니다. 이 예제에서는 Red와 Blue 변형을 가진 ShirtColor라는 enum 을 사용합니다 (단순화를 위해 사용 가능한 색상 수를 제한). 회사의 재고는 현재 재고에 있는 셔츠 색상을 나타내는 Vec<ShirtColor> 필드인 shirts를 가진 Inventory 구조체로 나타냅니다. Inventory에 정의된 giveaway 메서드는 무료 셔츠 당첨자의 선택적 셔츠 색상 선호도를 가져와서 해당 사람이 받게 될 셔츠 색상을 반환합니다. 이 설정은 Listing 13-1 에 나와 있습니다.
파일 이름: src/main.rs
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}
struct Inventory {
shirts: Vec<ShirtColor>,
}
impl Inventory {
fn giveaway(
&self,
user_preference: Option<ShirtColor>,
) -> ShirtColor {
1 user_preference.unwrap_or_else(|| self.most_stocked())
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}
fn main() {
let store = Inventory {
2 shirts: vec![
ShirtColor::Blue,
ShirtColor::Red,
ShirtColor::Blue,
],
};
let user_pref1 = Some(ShirtColor::Red);
3 let giveaway1 = store.giveaway(user_pref1);
println!(
"The user with preference {:?} gets {:?}",
user_pref1, giveaway1
);
let user_pref2 = None;
4 let giveaway2 = store.giveaway(user_pref2);
println!(
"The user with preference {:?} gets {:?}",
user_pref2, giveaway2
);
}
Listing 13-1: 셔츠 회사 증정 행사
main에 정의된 store는 이 한정판 프로모션을 위해 배포할 파란색 셔츠 2 개와 빨간색 셔츠 1 개를 가지고 있습니다 [2]. 빨간색 셔츠를 선호하는 사용자와 [3] 선호하는 색상이 없는 사용자 [4]에 대해 giveaway 메서드를 호출합니다.
다시 말하지만, 이 코드는 여러 가지 방법으로 구현할 수 있으며, 여기서는 클로저에 집중하기 위해 giveaway 메서드의 본문을 제외하고 이미 배운 개념을 고수했습니다. giveaway 메서드에서 Option<ShirtColor> 유형의 매개변수로 사용자 선호도를 가져와 user_preference에서 unwrap_or_else 메서드를 호출합니다 [1]. Option<T>에 대한 unwrap_or_else 메서드는 표준 라이브러리에 의해 정의됩니다. 이 메서드는 인수를 하나 받습니다. 즉, 인수가 없는 클로저로, 값 T를 반환합니다 (이 경우 ShirtColor인 Option<T>의 Some 변형에 저장된 동일한 유형). 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의 구현은 결과가 필요한 경우 나중에 클로저를 평가합니다.
이 코드를 실행하면 다음이 출력됩니다.
The user with preference Some(Red) gets Red
The user with preference None gets Blue
여기서 한 가지 흥미로운 점은 현재 Inventory 인스턴스에서 self.most_stocked()를 호출하는 클로저를 전달했다는 것입니다. 표준 라이브러리는 우리가 정의한 Inventory 또는 ShirtColor 유형이나 이 시나리오에서 사용하려는 로직에 대해 아무것도 알 필요가 없었습니다. 클로저는 self Inventory 인스턴스에 대한 불변 참조를 캡처하고 우리가 unwrap_or_else 메서드에 지정한 코드와 함께 전달합니다. 반면에 함수는 이러한 방식으로 환경을 캡처할 수 없습니다.
함수와 클로저 사이에는 더 많은 차이점이 있습니다. 클로저는 일반적으로 fn 함수처럼 매개변수 또는 반환 값의 타입을 주석 처리할 필요가 없습니다. 함수에는 타입 어노테이션이 필요합니다. 왜냐하면 타입은 사용자에게 노출되는 명시적 인터페이스의 일부이기 때문입니다. 이 인터페이스를 엄격하게 정의하는 것은 모든 사람이 함수가 사용하는 값과 반환하는 값의 타입에 동의하도록 보장하는 데 중요합니다. 반면에 클로저는 이와 같이 노출된 인터페이스에서 사용되지 않습니다. 클로저는 변수에 저장되고 이름을 지정하지 않고 라이브러리 사용자에게 노출하지 않고 사용됩니다.
클로저는 일반적으로 짧고 임의의 시나리오가 아닌 좁은 컨텍스트 내에서만 관련이 있습니다. 이러한 제한된 컨텍스트 내에서 컴파일러는 대부분의 변수 타입 (컴파일러가 클로저 타입 어노테이션도 필요로 하는 드문 경우도 있습니다) 의 타입을 추론할 수 있는 방식과 유사하게 매개변수와 반환 타입의 타입을 추론할 수 있습니다.
변수와 마찬가지로, 엄격하게 필요한 것보다 더 장황해지는 대가로 명시성과 명확성을 높이려면 타입 어노테이션을 추가할 수 있습니다. 클로저에 대한 타입 어노테이션은 Listing 13-2 에 표시된 정의와 같습니다. 이 예제에서는 Listing 13-1 에서 했던 것처럼 클로저를 인수로 전달하는 위치에서 정의하는 대신 클로저를 정의하고 변수에 저장합니다.
파일 이름: src/main.rs
let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
Listing 13-2: 클로저에서 선택적 매개변수 및 반환 값 타입의 타입 어노테이션 추가
타입 어노테이션을 추가하면 클로저의 구문이 함수의 구문과 더 유사해 보입니다. 여기서는 매개변수에 1 을 더하는 함수와 동일한 동작을 하는 클로저를 정의하여 비교합니다. 관련 부분을 정렬하기 위해 몇 개의 공백을 추가했습니다. 이것은 클로저 구문이 파이프 사용과 선택적인 구문의 양을 제외하고 함수 구문과 어떻게 유사한지 보여줍니다.
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 줄은 타입을 사용으로부터 추론할 수 있도록 클로저를 평가해야 컴파일할 수 있습니다. 이것은 Vec에 타입을 추론할 수 있도록 Rust 가 Vec에 타입 어노테이션이나 일부 타입의 값을 삽입해야 하는 let v = Vec::new();와 유사합니다.
클로저 정의의 경우 컴파일러는 각 매개변수와 반환 값에 대해 하나의 구체적인 타입을 추론합니다. 예를 들어, Listing 13-3 은 매개변수로 받은 값을 반환하는 짧은 클로저의 정의를 보여줍니다. 이 클로저는 이 예제를 제외하고는 그다지 유용하지 않습니다. 정의에 타입 어노테이션을 추가하지 않았다는 점에 유의하십시오. 타입 어노테이션이 없으므로, 여기에서 처음 String으로 수행한 것처럼 모든 타입으로 클로저를 호출할 수 있습니다. 그런 다음 정수로 example_closure를 호출하려고 하면 오류가 발생합니다.
파일 이름: src/main.rs
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
Listing 13-3: 타입이 추론된 클로저를 두 개의 다른 타입으로 호출하려고 시도
컴파일러는 다음과 같은 오류를 제공합니다.
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
String 값으로 example_closure를 처음 호출할 때 컴파일러는 x의 타입과 클로저의 반환 타입을 String으로 추론합니다. 그런 다음 해당 타입은 example_closure의 클로저에 고정되고, 동일한 클로저로 다른 타입을 사용하려고 시도하면 타입 오류가 발생합니다.
클로저는 환경에서 세 가지 방식으로 값을 캡처할 수 있으며, 이는 함수가 매개변수를 가져올 수 있는 세 가지 방식과 직접적으로 매핑됩니다. 불변으로 빌리기, 가변으로 빌리기, 소유권을 가져오기입니다. 클로저는 캡처된 값으로 함수 본문이 수행하는 작업에 따라 이러한 방식 중 어느 것을 사용할지 결정합니다.
Listing 13-4 에서, 값을 출력하기 위해 불변 참조만 필요하기 때문에 list라는 벡터에 대한 불변 참조를 캡처하는 클로저를 정의합니다.
파일 이름: src/main.rs
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
1 let only_borrows = || println!("From closure: {:?}", list);
println!("Before calling closure: {:?}", list);
2 only_borrows();
println!("After calling closure: {:?}", list);
}
Listing 13-4: 불변 참조를 캡처하는 클로저 정의 및 호출
이 예제는 또한 변수가 클로저 정의에 바인딩될 수 있음 [1]을 보여주며, 나중에 변수 이름이 함수 이름인 것처럼 변수 이름과 괄호를 사용하여 클로저를 호출할 수 있습니다 [2].
list에 대한 여러 개의 불변 참조를 동시에 가질 수 있으므로, 클로저 정의 전, 클로저 정의 후, 클로저 호출 전, 클로저 호출 후에도 list에 계속 접근할 수 있습니다. 이 코드는 컴파일되고 실행되며 다음을 출력합니다.
Before defining closure: [1, 2, 3]
Before calling closure: [1, 2, 3]
From closure: [1, 2, 3]
After calling closure: [1, 2, 3]
다음으로, Listing 13-5 에서 클로저 본문을 변경하여 list 벡터에 요소를 추가합니다. 이제 클로저는 가변 참조를 캡처합니다.
파일 이름: 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);
}
Listing 13-5: 가변 참조를 캡처하는 클로저 정의 및 호출
이 코드는 컴파일되고 실행되며 다음을 출력합니다.
Before defining closure: [1, 2, 3]
After calling closure: [1, 2, 3, 7]
borrows_mutably 클로저의 정의와 호출 사이에 더 이상 println!이 없다는 점에 유의하십시오. borrows_mutably가 정의되면 list에 대한 가변 참조를 캡처합니다. 클로저를 호출한 후에는 클로저를 다시 사용하지 않으므로 가변 빌림이 종료됩니다. 클로저 정의와 클로저 호출 사이에는 가변 빌림이 있는 경우 다른 빌림이 허용되지 않으므로 출력을 위한 불변 빌림이 허용되지 않습니다. 거기에 println!을 추가하여 어떤 오류 메시지가 나오는지 확인해 보세요!
클로저 본문이 엄격하게 소유권이 필요하지 않더라도 클로저가 환경에서 사용하는 값의 소유권을 가져오도록 강제하려면 매개변수 목록 앞에 move 키워드를 사용할 수 있습니다.
이 기술은 주로 클로저를 새 스레드로 전달하여 새 스레드가 데이터를 소유하도록 데이터를 이동할 때 유용합니다. 스레드와 스레드를 사용하려는 이유에 대해서는 동시성에 대해 이야기할 때 16 장에서 자세히 설명하겠지만, 지금은 move 키워드가 필요한 클로저를 사용하여 새 스레드를 생성하는 방법을 간략하게 살펴보겠습니다. Listing 13-6 은 메인 스레드 대신 새 스레드에서 벡터를 출력하도록 수정된 Listing 13-4 를 보여줍니다.
파일 이름: src/main.rs
use std::thread;
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
1 thread::spawn(move || {
2 println!("From thread: {:?}", list)
}).join().unwrap();
}
Listing 13-6: 스레드에 대한 클로저가 list의 소유권을 가져오도록 강제하기 위해 move 사용
새 스레드를 생성하고 스레드에 인수로 실행할 클로저를 제공합니다. 클로저 본문은 목록을 출력합니다. Listing 13-4 에서 클로저는 출력을 위해 필요한 list에 대한 최소한의 접근 권한이므로 불변 참조를 사용하여 list를 캡처했습니다. 이 예제에서는 클로저 본문이 여전히 불변 참조만 필요하지만 [2], 클로저 정의의 시작 부분에 move 키워드 [1]를 넣어 list가 클로저로 이동해야 함을 지정해야 합니다. 새 스레드는 메인 스레드의 나머지 부분이 완료되기 전에 완료될 수 있거나, 메인 스레드가 먼저 완료될 수 있습니다. 메인 스레드가 list의 소유권을 유지하지만 새 스레드 전에 종료되어 list를 삭제하면 스레드의 불변 참조가 유효하지 않게 됩니다. 따라서 컴파일러는 참조가 유효하도록 list가 새 스레드에 제공된 클로저로 이동하도록 요구합니다. move 키워드를 제거하거나 클로저가 정의된 후 메인 스레드에서 list를 사용하여 어떤 컴파일러 오류가 발생하는지 확인해 보세요!
클로저가 클로저가 정의된 환경에서 참조를 캡처하거나 값의 소유권을 캡처한 후 (따라서 클로저 안으로 이동하는 모든 것에 영향을 미침), 클로저 본문의 코드는 클로저가 나중에 평가될 때 참조 또는 값에 어떤 일이 발생하는지 정의합니다 (따라서 클로저 밖으로 이동하는 모든 것에 영향을 미침).
클로저 본문은 다음 중 하나를 수행할 수 있습니다. 캡처된 값을 클로저 밖으로 이동, 캡처된 값을 변경, 값을 이동하거나 변경하지 않음, 또는 처음부터 환경에서 아무것도 캡처하지 않음.
클로저가 환경에서 값을 캡처하고 처리하는 방식은 클로저가 구현하는 트레이트에 영향을 미치며, 트레이트는 함수와 구조체가 사용할 수 있는 클로저의 종류를 지정할 수 있는 방법입니다. 클로저는 클로저의 본문이 값을 처리하는 방식에 따라 이러한 Fn 트레이트 중 하나, 두 개 또는 세 개 모두를 부가적인 방식으로 자동으로 구현합니다.
FnOnce는 한 번 호출할 수 있는 클로저에 적용됩니다. 모든 클로저는 최소한 이 트레이트를 구현합니다. 왜냐하면 모든 클로저를 호출할 수 있기 때문입니다. 본문에서 캡처된 값을 이동하는 클로저는 다른 Fn 트레이트가 아닌 FnOnce만 구현합니다. 왜냐하면 한 번만 호출할 수 있기 때문입니다.FnMut는 본문에서 캡처된 값을 이동하지 않지만 캡처된 값을 변경할 수 있는 클로저에 적용됩니다. 이러한 클로저는 두 번 이상 호출할 수 있습니다.Fn은 본문에서 캡처된 값을 이동하지 않고 캡처된 값을 변경하지 않으며, 환경에서 아무것도 캡처하지 않는 클로저에 적용됩니다. 이러한 클로저는 환경을 변경하지 않고 두 번 이상 호출할 수 있으며, 이는 클로저를 여러 번 동시에 호출하는 경우와 같은 경우에 중요합니다.Listing 13-1 에서 사용한 Option<T>의 unwrap_or_else 메서드의 정의를 살펴보겠습니다.
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}
T는 Option의 Some 변형에 있는 값의 타입을 나타내는 제네릭 타입임을 기억하십시오. 해당 타입 T는 또한 unwrap_or_else 함수의 반환 타입입니다. 예를 들어, Option<String>에서 unwrap_or_else를 호출하는 코드는 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<Vec<T>>값에서unwrap_or_else(Vec::new)를 호출하여 값이None인 경우 새 빈 벡터를 얻을 수 있습니다.
이제 슬라이스에 정의된 표준 라이브러리 메서드 sort_by_key를 살펴보고 unwrap_or_else와 어떻게 다른지, 그리고 sort_by_key가 트레이트 바운드에 대해 FnOnce 대신 FnMut를 사용하는 이유를 살펴보겠습니다. 클로저는 고려 중인 슬라이스의 현재 항목에 대한 참조 형태의 인수를 하나 받아서 정렬할 수 있는 타입 K의 값을 반환합니다. 이 함수는 각 항목의 특정 속성으로 슬라이스를 정렬하려는 경우에 유용합니다. Listing 13-7 에서 Rectangle 인스턴스 목록이 있으며 sort_by_key를 사용하여 width 속성별로 낮은 값에서 높은 값으로 정렬합니다.
파일 이름: src/main.rs
#[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);
}
Listing 13-7: sort_by_key를 사용하여 너비별로 사각형 정렬
이 코드는 다음을 출력합니다.
[
Rectangle {
width: 3,
height: 5,
},
Rectangle {
width: 7,
height: 12,
},
Rectangle {
width: 10,
height: 1,
},
]
sort_by_key가 FnMut 클로저를 사용하도록 정의된 이유는 슬라이스의 각 항목에 대해 한 번씩 클로저를 여러 번 호출하기 때문입니다. 클로저 |r| r.width는 환경에서 아무것도 캡처, 변경 또는 이동하지 않으므로 트레이트 바운드 요구 사항을 충족합니다.
반대로, Listing 13-8 은 환경에서 값을 이동하기 때문에 FnOnce 트레이트만 구현하는 클로저의 예제를 보여줍니다. 컴파일러는 이 클로저를 sort_by_key와 함께 사용하도록 허용하지 않습니다.
파일 이름: src/main.rs
--snip--
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);
}
Listing 13-8: sort_by_key와 함께 FnOnce 클로저를 사용하려는 시도
이것은 list를 정렬할 때 sort_by_key가 호출되는 횟수를 (작동하지 않는) 억지로 세는, 엉성하고 복잡한 방법입니다. 이 코드는 클로저의 환경에서 String인 value를 sort_operations 벡터로 푸시하여 이 카운팅을 시도합니다. 클로저는 value를 캡처한 다음 value의 소유권을 sort_operations 벡터로 전송하여 value를 클로저 밖으로 이동합니다. 이 클로저는 한 번 호출할 수 있습니다. 두 번째로 호출하려고 하면 value가 다시 sort_operations에 푸시될 환경에 더 이상 없기 때문에 작동하지 않습니다! 따라서 이 클로저는 FnOnce만 구현합니다. 이 코드를 컴파일하려고 하면 클로저가 FnMut를 구현해야 하므로 value를 클로저 밖으로 이동할 수 없다는 오류가 발생합니다.
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
오류는 클로저 본문에서 value를 환경 밖으로 이동하는 줄을 가리킵니다. 이 문제를 해결하려면 환경에서 값을 이동하지 않도록 클로저 본문을 변경해야 합니다. 환경에서 카운터를 유지하고 클로저 본문에서 해당 값을 증가시키는 것이 sort_by_key가 호출되는 횟수를 세는 더 간단한 방법입니다. Listing 13-9 의 클로저는 sort_by_key와 함께 작동합니다. 왜냐하면 num_sort_operations 카운터에 대한 가변 참조만 캡처하고 두 번 이상 호출할 수 있기 때문입니다.
파일 이름: src/main.rs
--snip--
fn main() {
--snip--
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
);
}
Listing 13-9: sort_by_key와 함께 FnMut 클로저를 사용하는 것이 허용됩니다.
Fn 트레이트는 클로저를 사용하는 함수 또는 타입을 정의하거나 사용할 때 중요합니다. 다음 섹션에서는 반복자에 대해 논의합니다. 많은 반복자 메서드는 클로저 인수를 사용하므로 계속 진행하면서 이러한 클로저 세부 정보를 염두에 두십시오!
축하합니다! 환경을 캡처하는 익명 함수인 클로저 (Closures: Anonymous Functions That Capture Their Environment) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.