JS, TS랑 비교하며 Rust 공부하기 (9)
클로저
러스트의 클로저는 변수에 저장하거나 다른 함수의 인수로 전달할 수 있는 익명 함수다. 함수와 달리 정의된 스코프의 변수들을 캡처할 수 있다.
매개변수나 반환 값의 타입을 생략할 수 있으며, 컴파일러가 이를 자동으로 추론한다. 단, 추론된 타입은 한 번 결정되면 고정되므로 다른 타입으로 호출하면 컴파일 에러가 발생한다.
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); // error
값을 캡처하는 방식
값을 불변으로 빌려올 때는 불변 참조자를 캡처한다.
let list = vec![1, 2, 3];
let only_borrows = || println!("From closure: {:?}", list);
println!("Before calling closure: {:?}", list); // [1, 2, 3]
only_borrows(); // [1, 2, 3]
println!("After calling closure: {:?}", list); // [1, 2, 3]
값을 가변으로 빌려오려면 변수 자체를 가변으로 선언해야 하며, 클로저는 가변 참조자를 캡처한다.
let mut list = vec![1, 2, 3];
let mut borrows_mutably = || list.push(7);
borrows_mutably();
println!("After calling closure: {:?}", list); // [1, 2, 3, 7]
값의 소유권을 클로저로 이동시키고 싶을 때는 move 키워드를 사용한다. 이 방식은 멀티스레딩 환경에서 자주 사용된다.
use std::thread;
let list = vec![1, 2, 3];
thread::spawn(move || println!("From thread: {:?}", list)) // [1, 2, 3]
.join()
.unwrap();
Fn 트레이트
클로저는 내부에서 값을 어떻게 사용하는지에 따라 Fn, FnMut, FnOnce 트레이트 중 하나 이상을 자동으로 구현한다.
Fn은 캡처한 값을 변경하거나 이동하지 않고 사용할 수 있는 클로저에 적용된다. 이 클로저는 동시에 여러 번 호출될 수 있다.
fn call_fn<F: Fn()>(f: F) {
f();
}
let name = String::from("Alice");
let greet = || println!("Hello, {}", name); // 불변 캡처
call_fn(greet);
call_fn(greet);
FnMut은 캡처한 값을 변경할 수 있지만, 소유권을 이동시키지는 않는 클로저에 적용된다. 이 클로저는 여러 번 호출될 수 있다.
fn call_fn_mut<F: FnMut()>(mut f: F) {
f();
}
let mut count = 0;
let mut inc = || count += 1; // 가변 캡처
call_fn_mut(inc);
call_fn_mut(inc);
FnOnce는 클로저가 캡처한 변수의 소유권을 가져가는 경우에만 적용된다. 이 클로저는 내부에서 캡처한 변수를 move로 가져가기 때문에, 한 번 호출된 후에는 그 변수를 다시 사용할 수 없으며, 따라서 한 번만 호출될 수 있다.
fn call_fn_once<F: FnOnce()>(f: F) {
f();
}
let name = String::from("Alice");
let consume = move || drop(name); // move 캡처
call_fn_once(consume);
클로저 반환
클로저는 트레이트로 표현되므로 클로저를 직접 반환할 수 없으므로 트레이트 객체와 포인터를 이용해야 한다.
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}