클로저(Closure)

클로저 혹은 람다표현식은 익명타입입니다. 이들은 Fn,FnMut, FnOnce 라는 특별한 트레잇을 구현합니다:

fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 {
    println!("Calling function on {input}");
    func(input)
}

fn main() {
    let add_3 = |x| x + 3;
    println!("add_3: {}", apply_with_log(add_3, 10));
    println!("add_3: {}", apply_with_log(add_3, 20));

    let mut v = Vec::new();
    let mut accumulate = |x: i32| {
        v.push(x);
        v.iter().sum::<i32>()
    };
    println!("accumulate: {}", apply_with_log(&mut accumulate, 4));
    println!("accumulate: {}", apply_with_log(&mut accumulate, 5));

    let multiply_sum = |x| x * v.into_iter().sum::<i32>();
    println!("multiply_sum: {}", apply_with_log(multiply_sum, 3));
}
This slide should take about 20 minutes.

Fn(예를 들어 add_3)은 캡처된 값을 소모도 변경도 하지 않고, 혹은 어떤 것도 캡쳐하지 않았을 수도 있기 때문에 동시에 여러번 호출할 수 있습니다.

FnMut(예를 들어 accumulate)는 캡처된 값을 변경할 수 있으므로 여러 번 호출은 가능하지만 동시에 호출 할 수는 없습니다.

FnOnce (예를 들어 multiply_sum)는 한번만 호출되며 캡처된 값을 소모합니다.

FnMutFnOnce의 하위타입입니다. FnFnMutFnOnce의 하위 타입입니다. 즉, FnMutFnOnce가 호출되는 곳이면 어디서나 사용 할 수 있고 FnFnMutFnOnce가 호출되는 곳이면 어디든 사용할 수 있습니다.

When you define a function that takes a closure, you should take FnOnce if you can (i.e. you call it once), or FnMut else, and last Fn. This allows the most flexibility for the caller.

In contrast, when you have a closure, the most flexible you can have is Fn (it can be passed everywhere), then FnMut, and lastly FnOnce.

컴파일러는 클로저가 무엇을 캡쳐하는지에 따라 Copy(예를 들어 add_3)과 Clone(예를 들어 multiply_sum)을 알아서 추론합니다.

기본적으로 클로져는, 가능하다면, 참조를 사용하여 캡쳐를 합니다. move 키워드를 쓰면 값으로 캡쳐가 됩니다.

fn make_greeter(prefix: String) -> impl Fn(&str) {
    return move |name| println!("{} {}", prefix, name);
}

fn main() {
    let hi = make_greeter("Hi".to_string());
    hi("Greg");
}