# 클로저의 타입 추론과 캡처

러스트에서 클로저는 변수나 값을 주변 스코프로부터 캡처해 사용한다. 클로저는 기본적으로 함수와 유사하게 인자를 받고 결과를 반환할 수 있지만, 스코프 밖에 있는 값들에도 접근할 수 있다는 점에서 큰 차이가 있다. 이때 어떻게 캡처할지가 자동으로 결정되는 것도 특징이다. 클로저는 함수를 나타내는 세 가지 트레이트인 Fn, FnMut, FnOnce를 만족할 수 있으며, 어떤 트레이트를 구현할지는 컴파일 타임에 클로저의 사용 방식에 따라 자동으로 정해진다.

러스트의 클로저는 일반적으로 ‘타입 명시 없이도’ 사용할 수 있는데, 이는 컴파일러가 문맥과 사용처에 따라 클로저의 타입(매개변수 타입, 반환 타입, 캡처 모드 등)을 추론하기 때문이다. 만약 클로저가 여러 위치에서 다르게 호출되어 타입을 명확히 추정하기 어려운 상황이라면, 오류가 발생하거나 더 구체적인 타입 표기를 요구받을 수 있다.

클로저의 캡처 방식에는 세 가지가 있다. 가장 엄격한 요구사항부터 살펴보면, 클로저가 변수를 완전히 소유해야 하는 경우에는 FnOnce를 구현하며, 소유가 필요하지 않고 클로저 내부에서 가변 접근을 해야 한다면 FnMut를 구현한다. 마지막으로, 클로저 내부에서 읽기 전용 접근만 한다면 Fn을 구현한다. 컴파일러는 클로저가 외부 변수를 어떻게 사용하는지 살펴보고, 가장 ‘가벼운’ 캡처 트레이트부터 적용 가능한지 검사한다. 예를 들어 읽기 접근만 있으면 Fn이 되고, 가변 접근이 있으면 FnMut, 값의 이동이 필요한 상황이면 FnOnce로 결정된다.

캡처 자체도 참조와 값 이동을 자동으로 판단한다. 러스트는 클로저가 참조를 사용하는 것이 가능한 한 효율적인 방식이므로, 변수 사용 방식이 단순히 읽기라면 불필요한 소유 이전 없이 참조로만 캡처하려 한다. 예를 들어 클로저 내부에서 외부 변수를 변경하지 않고 단순히 읽기만 한다면, 컴파일러는 그 변수를 불변 참조로 캡처한다. 만약 클로저 안에서 해당 변수를 변경해야 하는 코드가 있다면, 가변 참조로 캡처가 일어난다. 변수의 소유권을 반드시 가져와야 하는 상황이라면(예: move 키워드를 사용하거나, 해당 변수가 힙 데이터에 대한 소유권을 가져와야 하는 상황) 값이 이동(capture by value)된다.

실제 예시를 살펴보면 다음과 같은 코드가 있을 수 있다.

```
fn main() {
    let x = 10;
    let print_x = || println!("x is {}", x);
    print_x();
    print_x();
}
```

이 코드에서 클로저 print\_x는 외부 변수 x를 읽기만 하므로, 컴파일러는 print\_x를 Fn 트레이트로 추론하고 x를 불변 참조로 캡처한다. 따라서 print\_x를 여러 번 호출해도 x의 값은 변하지 않으며, 클로저 역시 x의 불변 참조를 사용할 수 있다.

반면 다음 코드에서는 상황이 달라진다.

```
fn main() {
    let mut count = 0;
    let mut inc_count = || {
        count += 1;
        println!("count: {}", count);
    };
    inc_count();
    inc_count();
}
```

여기서는 클로저 내부에서 외부 변수 count를 변경하고 있다. 따라서 클로저는 count를 불변 참조로는 캡처할 수 없다. 컴파일러는 클로저가 외부 변수를 변경해야 함을 감지하고, inc\_count 클로저를 FnMut로 추론하고 count를 가변 참조로 캡처한다. 이 때문에 inc\_count를 호출할 때마다 count의 값이 증가하고, 이 값은 클로저가 끝난 뒤에도 유지된다.

만약 클로저가 어떤 값을 완전히 소유해야만 하는 상황이라면, 컴파일러는 FnOnce로 추론한다. 예를 들어 클로저가 동작하면서 외부 변수의 소유권을 가져가거나, 한 번만 호출된 뒤로는 그 변수를 다시 사용할 수 없게 만들어야 하는 경우가 이에 해당한다. move 키워드를 사용하는 경우가 대표적이다.

```
fn main() {
    let s = String::from("hello");
    let consume_s = move || {
        println!("moved value: {}", s);
    };
    consume_s();
    // 여기서 s는 이미 move로 인해 클로저로 소유권이 이동되어 더 이상 사용할 수 없음
}
```

이 코드에서 클로저 consume\_s는 s를 이동시켜 소유하기 때문에, consume\_s를 한 번 호출하고 나면 s의 소유권은 클로저로 넘어가고 main 함수 스코프에서 s를 더 이상 사용할 수 없다. 컴파일러는 이와 같은 상황에서 클로저가 외부 변수를 move로 가져갈 필요가 있음을 인식하고, FnOnce로 추론한다. 다시 말해 한 번만 안전하게 호출될 수 있는 클로저로 본다.

클로저가 어떠한 Fn 계열 트레이트를 구현할지는 클로저의 사용 방식과 스코프 밖 변수를 어떻게 다루는지에 달려 있다. 이를 Rust가 자동으로 처리해 주므로 일반적으로 타입 표기를 명시적으로 하지 않고도 원하는 행동을 얻을 수 있다. 그러나 일부 복잡한 상황이나 제네릭 인자의 제약을 설정해야 하는 경우에는 Fn, FnMut, FnOnce를 명시적으로 제한하거나 클로저의 타입을 추론할 힌트를 제공하기도 한다.

정리하자면, 러스트의 클로저는 먼저 사용 방식에 따라 외부 변수를 불변 참조, 가변 참조, 혹은 값 이동으로 캡처하며, 그에 따라 자동으로 Fn, FnMut, FnOnce 중 하나를 구현한다고 볼 수 있다. 또한 클로저가 매개변수와 반환값을 어떻게 사용하고 있는지 역시 문맥을 통해 자동으로 추론하기 때문에, 보통은 개발자가 구체적인 타입을 명시하지 않고도 매우 간결하고 유연한 코드를 작성할 수 있다. 이러한 클로저의 타입 추론과 캡처 방식은 러스트가 함수형 프로그래밍 스타일을 지원하면서도, 메모리 안전성과 효율성을 동시에 보장하는 데 큰 기여를 한다.
