# Fn, FnMut, FnOnce 트레이트

Fn, FnMut, FnOnce는 러스트에서 클로저(Closure) 혹은 콜백처럼 호출 가능한 객체를 표현할 때 사용하는 트레이트이며, 그 차이를 명확히 이해하는 것은 클로저를 안전하게 사용하기 위한 핵심이다. 클로저는 주변 스코프의 변수를 어떻게 캡처하는지에 따라 이 세 가지 트레이트 중 하나 이상의 구현체가 될 수 있다. 이 트레이트들은 호출 가능한 객체(함수, 클로저 등)가 제공해야 할 호출(call) 방식의 차이를 표현하며, 내부 상태의 변경 가능성과 호출 횟수 제약을 구분한다. FnOnce, FnMut, Fn은 각각 더 강력한 제약을 만족할 수록 상위 트레이트를 모두 만족하게 된다.

클로저가 어떤 트레이트를 구현하게 되는지 살펴보면, 클로저 내에서 캡처한 변수를 어떻게 다루는지에 달려 있다. 단 한 번만 소비해야 하는 값들을 클로저가 소유해야 한다면 FnOnce가 되며, 반복해서 호출해도 문제없는 불변 참조를 활용한다면 Fn이 될 수 있다. 그 중간에서 클로저의 내부 상태를 변경해야 한다면 FnMut가 된다.

FnOnce는 가장 제한이 적은 형태다. 어떤 환경 변수를 move(이동)로 캡처하는 클로저는 캡처한 변수를 한 번만 사용할 수 있으므로, 호출도 한 번만 허용된다. FnMut는 불변 참조가 아닌 가변 참조로 환경을 캡처하여 상태를 변경할 수 있는 클로저 형태를 나타낸다. Fn은 불변 참조로만 캡처된 상태를 가정하므로, 여러 번 호출해도 변하지 않는 내부를 가진다.

다음 예제 코드는 FnOnce를 요구하는 상황을 보여준다. 클로저가 환경의 값을 소유해야 하며, 그로 인해 한 번만 호출 가능한 함수를 필요로 할 때 자주 나타난다.

```
let s = String::from("Hello");
let consume_once = move || {
    println!("소유된 문자열: {}", s);
};
// 아래 호출에서 s는 이미 클로저로 이동되었으므로 consume_once는 FnOnce를 구현한다
consume_once(); 
```

FnMut를 구현하는 클로저는 보통 반복 호출하지만, 그 내부에서 상태 변경이 필요한 경우에 사용된다. 가령, 누적 합계를 계산하면서 클로저 스코프에 존재하는 변수를 갱신하는 경우를 생각해볼 수 있다.

```
let mut counter = 0;
let mut increment = || {
    counter += 1;
    counter
};
// FnMut는 가변 환경 캡처를 허용하므로 여러 번 호출 가능
println!("최초 호출: {}", increment());
println!("두 번째 호출: {}", increment());
```

Fn을 구현하는 클로저는 불변 참조를 통해 환경을 캡처하며, 여러 번 호출해도 내부 상태가 변하지 않는다. 불필요한 상태 변경 없이 참조를 읽기만 하므로 여러 번 호출해도 안전하다.

```
let greeting = String::from("안녕");
let print_greeting = || {
    println!("{}", greeting);
};
// 클로저는 greeting을 불변 참조로 캡처하므로 Fn 트레이트를 구현한다
print_greeting();
print_greeting();
```

이러한 세 가지 트레이트는 Iterators, Threads, Channels 등의 다양한 표준 라이브러리 API에서 폭넓게 사용된다. 예를 들어, 어떤 반복자 어댑터(map, filter, fold 등)가 FnMut로 정의된 클로저를 요구한다면, 그 클로저는 각 요소를 처리할 때마다 내부 상태를 갱신할 수 있어야 한다. 반면 특정 상황에서 FnOnce를 요구하는 경우는 클로저가 그 즉시 값의 소유권을 가져서 반복 호출이 불가능한 특수한 API일 때 나타난다. 일반적으로 클로저를 작성할 때는 명시적으로 Fn, FnMut, FnOnce를 구분하지 않아도 되며, 러스트 컴파일러가 상황에 맞춰 필요한 트레이트를 자동으로 추론해준다. 그러나 특정 상황에서 move 키워드를 써야 하거나, 클로저를 어떤 트레이트 객체로 저장할 때는 Fn, FnMut, FnOnce 여부가 코드의 동작 방식과 성능, 소유권에 영향을 준다.

FnOnce가 최상위 트레이트라는 점, FnMut가 FnOnce를 상속한다는 점, 그리고 Fn이 FnMut를 상속한다는 점을 기억하면 호출 가능 객체의 제약 관계를 이해할 수 있다. Fn은 가장 엄격한 접근 제약(불변 참조만 허용)이지만 그만큼 여러 번 호출될 수 있고, FnMut는 내부 값을 변경할 수 있지만 클로저를 재사용하려면 여전히 가변 참조가 필요하며, FnOnce는 사용 후 환경을 소모하므로 단 한 번만 호출 가능하다는 특성이 있다.

이러한 차이를 이해하면 클로저를 정의하거나 API에 전달할 때 발생할 수 있는 오류나 제약을 쉽게 예측할 수 있다. 클로저가 소유권을 가져야 하는 상황에서 Fn 혹은 FnMut로만 제한된 API를 사용하면 에러가 발생할 수 있고, 반대로 불변 참조로만 충분한데 불필요하게 move 키워드를 쓰면 클로저가 FnOnce로만 사용 가능해지는 상황이 발생할 수 있다. 트레이트 제약에 맞춰 적절히 캡처 방식을 정하는 것이 러스트에서 안전하고 효율적인 함수형 스타일의 코드를 작성하는 핵심이다.
