Rust 클로저 이해: Fn, FnMut 그리고 FnOnce에 대한 실질적 고찰
Emily Parker
Product Engineer · Leapcell

Rust 프로그래밍 언어에서 클로저는 익명 함수를 정의하고 주변 환경에서 변수를 캡처할 수 있게 해주는 강력하고 유연한 기능입니다. Rust의 클로저 시스템은 클로저가 캡처된 변수와 상호 작용하는 방식, 호출할 수 있는 횟수, 환경을 수정할 수 있는지 여부를 결정하는 세 가지 핵심 트레이트(Fn, FnMut, FnOnce)로 정의됩니다. 이러한 트레이트를 이해하는 것은 Rust의 클로저 메커니즘을 마스터하고 효율적이고 안전한 코드를 작성하는 데 매우 중요합니다.
이 기사에서는 관련 개념을 포괄적으로 학습할 수 있도록 코드 예제와 함께 정의, 사용 사례, 방법, 적용 가능한 시나리오 및 모범 사례를 포함하여 세 가지 트레이트(Fn, FnMut 및 FnOnce)에 대한 자세한 소개를 제공합니다.
Fn, FnMut 및 FnOnce란 무엇인가요?
Fn, FnMut 및 FnOnce는 클로저(또는 호출 가능한 객체)의 동작을 설명하기 위해 Rust의 표준 라이브러리에 정의된 세 가지 트레이트입니다. 주요 차이점은 캡처된 변수에 액세스하는 방법과 호출 시 소유권 규칙에 있습니다.
- FnOnce: 클로저를 한 번 호출할 수 있음을 나타냅니다. 호출된 후 클로저 자체가 소비되어 더 이상 사용할 수 없습니다.
- FnMut: 클로저를 여러 번 호출할 수 있으며 호출 시 캡처된 변수를 수정할 수 있음을 나타냅니다.
- Fn: 클로저를 여러 번 호출할 수 있으며 수정하지 않고 캡처된 변수만 읽음을 나타냅니다.
이러한 세 가지 트레이트 간에는 상속 관계가 있습니다.
- Fn은 FnMut에서 상속하고 FnMut는 FnOnce에서 상속합니다.
- 따라서 클로저가 Fn을 구현하면 FnMut 및 FnOnce도 자동으로 구현합니다. FnMut를 구현하면 FnOnce도 구현합니다.
각 트레이트의 정의
FnOnce
FnOnce
트레이트는 다음 서명으로 call_once
메서드를 정의합니다.
pub trait FnOnce<Args> { type Output; fn call_once(self, args: Args) -> Self::Output; }
- 특성:
call_once
는&self
또는&mut self
대신self
를 사용합니다. 즉, 클로저가 호출되면 자체 소유권을 이전하므로 한 번만 호출할 수 있습니다. - 사용 사례: 클로저가 캡처된 변수를 이동하거나 일회성 작업을 수행해야 하는 시나리오에 적합합니다.
FnMut
FnMut
트레이트는 다음 서명으로 call_mut
메서드를 정의합니다.
pub trait FnMut<Args>: FnOnce<Args> { fn call_mut(&mut self, args: Args) -> Self::Output; }
- 특성:
call_mut
는&mut self
를 사용하므로 클로저는 호출 시 내부 상태 또는 캡처된 변수를 수정할 수 있으며 여러 번 호출할 수 있습니다. - 사용 사례: 클로저가 여러 호출에서 환경을 수정해야 하는 시나리오에 적합합니다.
Fn
Fn
트레이트는 다음 서명으로 call
메서드를 정의합니다.
pub trait Fn<Args>: FnMut<Args> { fn call(&self, args: Args) -> Self::Output; }
- 특성:
call
은&self
를 사용합니다. 즉, 클로저는 자체 및 캡처된 변수를 변경 불가능하게 빌립니다. 환경을 수정하지 않고 여러 번 호출할 수 있습니다. - 사용 사례: 클로저를 여러 번 호출해야 하고 데이터만 읽는 시나리오에 적합합니다.
클로저가 이러한 트레이트를 구현하는 방법
Rust의 컴파일러는 캡처된 변수를 사용하는 방식에 따라 클로저가 구현하는 트레이트를 자동으로 결정합니다. 클로저는 다음 세 가지 방법으로 변수를 캡처할 수 있습니다.
- 값으로(이동): 클로저가 변수의 소유권을 가져옵니다.
- 가변 참조(
&mut
): 클로저가 변수에 대한 가변 참조를 캡처합니다. - 불변 참조(
&
): 클로저가 변수에 대한 불변 참조를 캡처합니다.
구현된 트레이트는 캡처된 변수가 사용되는 방식에 따라 달라집니다.
- FnOnce만 구현: 클로저가 캡처된 변수를 이동합니다.
- FnMut 및 FnOnce 구현: 클로저가 캡처된 변수를 수정합니다.
- Fn, FnMut 및 FnOnce 구현: 클로저가 캡처된 변수만 읽습니다.
코드 예제
FnOnce 구현 클로저
fn main() { let s = String::from("hello"); let closure = move || { drop(s); // s를 이동하고 삭제합니다. }; closure(); // 한 번 호출됨 // closure(); // 오류: 클로저가 소비되었습니다. }
설명: 클로저는 이동하여 s
를 캡처하고 호출될 때 삭제합니다. s
가 이동되었으므로 클로저는 한 번만 호출할 수 있으므로 FnOnce
만 구현합니다.
FnMut 구현 클로저
fn main() { let mut s = String::from("hello"); let mut closure = || { s.push_str(" world"); // s를 수정합니다. }; closure(); // 첫 번째 호출 closure(); // 두 번째 호출 println!("{}", s); // "hello world world"를 출력합니다. }
설명: 클로저는 가변 참조로 s
를 캡처하고 각 호출마다 수정합니다. 환경을 수정해야 하므로 FnMut
및 FnOnce
를 구현합니다.
Fn 구현 클로저
fn main() { let s = String::from("hello"); let closure = || { println!("{}", s); // 수정 없이 s를 읽습니다. }; closure(); // 첫 번째 호출 closure(); // 두 번째 호출 }
설명: 클로저는 불변 참조로 s
를 캡처하고 수정 없이 읽기만 합니다. 따라서 Fn
, FnMut
및 FnOnce
를 구현합니다.
함수 매개변수에서 이러한 트레이트 사용
클로저는 함수에 인수로 전달할 수 있으며 함수는 트레이트 바운드를 사용하여 필요한 클로저 동작을 지정해야 합니다.
FnOnce 사용
fn call_once<F>(f: F) where F: FnOnce(), { f(); } fn main() { let s = String::from("hello"); call_once(move || { drop(s); }); }
설명: call_once
는 FnOnce
클로저를 허용하고 한 번 호출하여 캡처된 변수를 이동하는 클로저에 적합합니다.
FnMut 사용
fn call_mut<F>(mut f: F) where F: FnMut(), { f(); f(); } fn main() { let mut s = String::from("hello"); call_mut(|| { s.push_str(" world"); }); println!("{}", s); // "hello world world"를 출력합니다. }
설명: call_mut
는 FnMut
클로저를 허용하고 두 번 호출합니다. 클로저는 캡처된 변수를 수정할 수 있습니다. f
는 mut
로 선언해야 합니다.
Fn 사용
fn call_fn<F>(f: F) where F: Fn(), { f(); f(); } fn main() { let s = String::from("hello"); call_fn(|| { println!("{}", s); }); }
설명: call_fn
은 Fn
클로저를 허용하고 두 번 호출합니다. 클로저는 캡처된 변수만 읽습니다.
각 트레이트를 사용하는 시기?
올바른 트레이트 선택은 클로저에 필요한 동작에 따라 달라집니다.
FnOnce
- 사용 사례: 클로저를 한 번만 호출하거나 캡처된 변수를 이동해야 합니다.
- 예: 소유권을 이전하는 일회성 작업입니다.
FnMut
- 사용 사례: 클로저를 여러 번 호출하고 캡처된 변수를 수정해야 합니다.
- 예: 카운터 또는 상태 업데이트입니다.
Fn
- 사용 사례: 클로저를 여러 번 호출해야 하고 캡처된 변수만 읽습니다.
- 예: 로깅 또는 데이터 쿼리입니다.
함수를 설계할 때 가장 허용적인 트레이트를 선택하면 유연성이 높아집니다. 예를 들어 FnOnce
는 모든 클로저를 허용하지만 호출을 제한하는 반면 Fn
은 여러 번 호출할 수 있지만 불변성이 필요합니다.
모범 사례
- Fn 선호: 클로저가 변수를 수정할 필요가 없는 경우 최대 호환성을 위해
Fn
을 사용합니다. - 수정이 필요한 경우 FnMut 사용: 클로저가 상태를 업데이트해야 하는 경우
FnMut
를 선택합니다. - 단일 사용 클로저에는 FnOnce 사용: 클로저가 변수를 이동하거나 일회성 작업을 수행하는 경우
FnOnce
를 사용합니다. - API에 적합한 트레이트 선택: 단일 사용 호출에는
FnOnce
를, 여러 읽기 전용 호출에는Fn
을, 수정이 있는 여러 호출에는FnMut
를 사용합니다. - 수명에 유의: 캡처된 변수가 대여 오류를 방지할 수 있을 만큼 오래 유지되도록 합니다.
Rust 프로젝트 호스팅을 위한 최고의 선택인 Leapcell입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 비용을 지불합니다. 요청도 없고 요금도 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용한 만큼만 지불합니다.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
손쉬운 확장성 및 고성능
- 손쉬운 동시성 처리를 위한 자동 확장.
- 제로 운영 오버헤드 — 구축에만 집중하십시오.
설명서에서 자세히 알아보세요!
X에서 팔로우하세요: @LeapcellHQ