환경을 캡처할 수 있는 익명 함수
클로저는 호출되는 스코프의 변수를 캡처할 수 있다.
이는 함수로는 할 수 없는 일이다.
클로저의 파라미터나 반환값의 타입 명시는 강제가 아니다.
컴파일러가 문맥 안에서 타입을 추론한다.
굳이 클로저의 타입을 표현하고 싶다면 함수처럼 타입을 명시하면 된다.
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 ;
표준 라이브러리는 Fn
, FnMut
, FnOnce
트레잇을 제공하고 모든 클로저는 이 트레잇 중 하나를 구현한다.
제너릭 파라미터와 Fn
트레잇을 사용해서 클로저를 구조체 필드에 넣을 수 있다.
u32
를 입력과 반환하는 클로저의 트레잇 바운드는 Fn(u32) -> u32
이다.
struct Cacher<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: Option<u32>,
}
함수는 Fn*
트레잇 세 개를 모두 구현한다.
환경에서 값을 캡처할 필요가 없을 때, Fn
트레잇 구현이 필요한 곳에서 클로저 대신 함수를 사용해도 된다.
아래의 equal_to_x
클로저는 자신이 정의된 스코프에 있는 변수 x
를 사용할 수 있다.
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
함수로 위와 동일하게 하려면 에러가 난다.
fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool { z == x }
let y = 4;
assert!(equal_to_x(y));
}
에러는 can't capture dynamic environment in a fn item; use the || { ... } closure form instead
라면서 친절하게도 클로저를 사용하라고 제안한다.
클로저가 환경에서 값을 캡처할 때, 캡처값을 저장할 메모리를 사용한다.
함수는 환경을 캡처하지 않아서 이런 오버헤드가 발생하지 않는다.
소유권 받기, 불변으로 빌려오기, 가변으로 빌려오는 방식이다.
-
FnOnce
는 캡처한 변수의 소유권을 클로저 안으로 옮긴다. 클로저가 동일한 변수에 대해 한 번만 소유권을 얻을 수 있어서Once
라는 이름이 들어갔다. -
Fn
은 캡처한 값을 불변으로 빌려온다. -
FnMut
은 값을 가변으로 빌려와서 환경을 변경할 수 있다.
클로저를 다른 스레드로 넘길 때, 새로운 스레드가 이동하는 데이터를 소유하도록 할 때 유용하다.
정수는 복사돼서 아래 예제는 벡터를 사용했다.
equal_to_x
클로저가 x
의 소유권을 가져가기 때문에 println!
문에 x
를 사용할 수 없어서 아래 코드는 컴파일 에러가 난다.
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}