Skip to content

클로저

Closure는 변수에 저장하거나 다른 함수에 인자로 넘길 수 있는 익명 함수이다. 한 곳에서 Closure를 만들고 실제 실행은 다른 곳에서 하도록 코드를 구성할 수 있게 한다.

상위 스코프 변수 참조

함수와 다르게 Closure는 호출되는 스코프로부터 변수들을 캡처할 수 있다.

변수를 참조하는 Closure를 정의했을 때, 해당 변수에 대한 소유권은 Closure가 가져간다.

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));
}
// error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
// } closure form instead
// --> src/main.rs
// |
// 4 | fn equal_to_x(z: i32) -> bool { z == x }
// | ^

타입 추론과 어노테이션

Closure는 fn 함수처럼 파라미터나 반환값의 타입을 명시할 것을 요구하지 않는다. 클로저는 보통 짧고 좁은 문맥 안에서 사용되기 때문에, 컴파일러가 파라미터와 리턴타입을 쉽게 추론해준다.

원한다면 타입을 명시하는 것도 가능하다. 아래 코드는 모두 같은 Closure를 나타내는 코드이다.

let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;

한 Closure에 대해 여러 타입으로 추론될 수 있도록 코드를 작성한다면 에러가 발생한다.

let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
// error[E0308]: mismatched types
// --> src/main.rs
// |
// | let n = example_closure(5);
// | ^ expected struct `std::string::String`, found
// integral variable
// |
// = note: expected type `std::string::String`
// found type `{integer}`

처음 String 값으로 example_closure을 호출하면 컴파일러는 x 의 타입과 클로저의 반환 타입을 String 으로 추론한다. 이 타입들이 example_closure에 있는 클로저에 고정되어서, 같은 클로저를 다른 타입으로 사용하면 타입 에러가 발생한다.

클로저와 안전성

FnOnce

Rust에서는 값을 버리는 행위를 하기 위해서 drop()을 호출할 수 있다.

let my_str = "hello".to_string();
let f = || drop(my_str);
f(); // Ok
f(); // Error : 이동된 값을 사용한다.

러스트 컴파일러는 여기서 값이 이동되는것을 알고 있다. 이는 인수를 갖지 않는 클로저가 컴파일 과정에서 Fn, FnOnce 트레이트를 따로 구현하기 때문이다.

trait Fn() -> R{
fn Call(&self) -> R; // 레퍼런스를 자기자신으로 가진다.
}
trait FnOnce() -> R{
fn call_once(self) -> R;
}

따라서 클로저가 처음 호출될 때만 안전한 경우 call_once()로 확장되고, 여러번 호출되도 되는경우 Call()로 확장된다.

FnMut

변경할 수 있는 데이터나 mut 레퍼런스를 가지고 있는 클로저가 있다.

trait FnMut() -> R{
fn call_once(&mut self) -> R;
}

FnMut 클로저는 mut 레퍼런스를 통해 호출되며, 값의 mut 접근 권한을 필요로 하지만 아무런 값을 drop하지 않는 클로저이다.

따라서 where F: FnMut()을 사용해서 명시해주는것이 좋다.


참고