캡처
클로저는 본질적으로 유연하며, 어노테이션 없이도 클로저가 작동하는 데 필요한 기능을 수행합니다. 이를 통해 캡처 방식이 사용 사례에 따라 유연하게 적응할 수 있으며, 때로는 이동(move)하고 때로는 빌림(borrow)을 수행합니다. 클로저는 다음과 같은 방식으로 변수를 캡처할 수 있습니다:
- 참조에 의한 방식:
&T - 가변 참조에 의한 방식:
&mut T - 값에 의한 방식:
T
클로저는 가급적 참조로 변수를 캡처하며, 필요한 경우에만 더 낮은 수준(이동 등)으로 내려갑니다.
fn main() {
use std::mem;
let color = String::from("초록");
// `color`를 출력하는 클로저입니다. 이 클로저는 즉시 `color`를 빌리고(`&`)
// 그 빌림과 클로저를 `print` 변수에 저장합니다. `print`가 마지막으로
// 사용될 때까지 빌린 상태가 유지됩니다.
//
// `println!`은 인자로 불변 참조만 요구하므로 더 이상의 제약을
// 가하지 않습니다.
let print = || println!("`color`: {}", color);
// 빌림을 사용하여 클로저를 호출합니다.
print();
// 클로저가 `color`에 대한 불변 참조만 가지고 있기 때문에,
// `color`를 다시 불변으로 빌릴 수 있습니다.
let _reborrow = &color;
print();
// `print`를 마지막으로 사용한 후에는 이동(move)이나 다시 빌리는 것이 허용됩니다
let _color_moved = color;
let mut count = 0;
// `count`를 증가시키는 클로저는 `&mut count`나 `count` 중 하나를 취할 수 있지만,
// `&mut count`가 덜 제한적이므로 이를 취합니다. 즉시 `count`를 빌립니다.
//
// 내부에 `&mut`가 저장되어 있기 때문에 `inc`에 `mut`가 필요합니다. 따라서
// 클로저를 호출하면 `count`가 수정되며, 이를 위해 `mut`가 필요합니다.
let mut inc = || {
count += 1;
println!("`count`: {}", count);
};
// 가변 빌림을 사용하여 클로저를 호출합니다.
inc();
// 클로저가 나중에 호출되기 때문에 여전히 `count`를 가변적으로 빌리고 있습니다.
// 다시 빌리려는 시도는 에러를 발생시킵니다.
// let _reborrow = &count;
// ^ TODO: 이 줄의 주석을 해제해 보세요.
inc();
// 클로저가 더 이상 `&mut count`를 빌릴 필요가 없습니다. 따라서
// 에러 없이 다시 빌리는 것이 가능합니다.
let _count_reborrowed = &mut count;
// 복사(copy)되지 않는 타입.
let movable = Box::new(3);
// `mem::drop`은 `T`를 요구하므로 값을 가져와야 합니다. 복사 타입은
// 원본을 그대로 둔 채 클로저 내부로 복사될 것입니다.
// 복사되지 않는 타입은 이동(move)해야 하므로 `movable`은 즉시
// 클로저 내부로 이동됩니다.
let consume = || {
println!("`movable`: {:?}", movable);
mem::drop(movable);
};
// `consume`은 변수를 소비하므로 한 번만 호출될 수 있습니다.
consume();
// consume();
// ^ TODO: 이 줄의 주석을 해제해 보세요.
}
수직 바(||) 앞에 move를 사용하면 클로저가 캡처된 변수의 소유권을 강제로 갖게 됩니다:
fn main() {
// `Vec`은 복사되지 않는 세맨틱을 가집니다.
let haystack = vec![1, 2, 3];
let contains = move |needle| haystack.contains(needle);
println!("{}", contains(&1));
println!("{}", contains(&4));
// println!("vec에는 {}개의 요소가 있습니다", haystack.len());
// ^ 위의 줄의 주석을 해제하면 컴파일 타임 에러가 발생합니다.
// 빌림 검사기(borrow checker)가 이동된 후의 변수 재사용을 허용하지 않기 때문입니다.
// 클로저의 시그니처에서 `move`를 제거하면 클로저가 `haystack` 변수를
// 불변으로 빌리게 되므로, `haystack`을 여전히 사용할 수 있고
// 위의 줄의 주석을 해제해도 에러가 발생하지 않습니다.
}