러스트는 함수형 언어에 영향을 받아 만들어졌다. 그래서 반복자와 클로저라는 특성을 갖고 있다.
클로저
클로저는 변수에 저장하거나 다른 함수에 인수로 전달할 수 있는 익명 함수이다. 클로저를 만들고 다른 곳에서 클로저를 호출해 평가할 수 있다. 클로저는 정의된 스코프에서 값을 캡처할 수 있다.
클로저로 환경 캡처하기
티셔츠 회사에서 메일링 리스트에 있는 사람들에게 한정 티셔츠를 주는 이벤트를 한다. 사람들은 좋아하는 색상을 설정해두면 해당 색상의 티셔츠를 받을 수 있고, 설정을 안 했다면 회사가 가장 많이 보유 중인 색상을 받는다.
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}
struct Inventory {
shirts: Vec<ShirtColor>,
}
impl Inventory {
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked())
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}
fn main() {
let store = Inventory {
shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
};
let user_pref1 = Some(ShirtColor::Red);
let giveaway1 = store.giveaway(user_pref1);
println!(
"The user with preference {:?} gets {:?}",
user_pref1, giveaway1
);
let user_pref2 = None;
let giveaway2 = store.giveaway(user_pref2);
println!(
"The user with preference {:?} gets {:?}",
user_pref2, giveaway2
);
}
main
에서 user_pref1
은 색상을 설정한 사람이고, user_pref2
는 색상을 설정하지 않은 사람이다. 각각은 giveaway
를 호출해 셔츠 색상을 반환한다.
giveaway
메서드는 설정을 Optoin<ShirtColor>
타입의 매개변수 user_preference
로 unwrap_or_else
메서드를 호출한다. 이 메서드는 인수로 ‘아무런 인수도 없고 T값을 반환하는 클로저’를 받는다. 만약 Some
이라면 Some
에 들어 있는 값을 반환하고, None
이라면 클로저를 호출해 클로저가 반환한 값을 반환한다.
unwrap_or_else
의 인수는 ||self.most_stocked()
라는 클로저 표현식을 지정했다. 클로저 본문은 self.most_stocked()
를 호출한다.
클로저는 self Inventory
인스턴스의 불변 참조자, 즉 바깥쪽 함수를 캡처해, 우리가 지정한 코드와 함께 이 값을 unwrap_or_else
메서드에 넘겨준다.
클로저 타입과 추론 명시
클로저는 이름 없이 라이브러리의 사용자들에게 노출되지 않은 채로 변수에 저장되고 사용된다.
아래처럼 타입을 명시할 수도 있다.
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
참조자 캡처와 소유권 이동
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
//list 벡터에 대한 불변 참조자를 캡처하는 클로저
let only_borrows = || println!("From closure: {:?}", list);
println!("Before calling closure: {:?}", list);
only_borrows();
println!("After calling closure: {:?}", list);
}
클로저는 함수가 매개변수를 취하는 세 가지 방식, 즉 불변으로 빌려오기, 가변으로 빌려오기, 소유권 이동에 기초해 캡처 방법을 결정한다.
위의 클로저는 list
벡터에 대한 불변 참조자를 캡처한다. 값을 출력하기 위한 불변 참조자만 필요한 상태이기 때문이다.
또한 only_borrows
라는 변수에 바인딩되었고, 변수 이름이 마치 함수 이름인 것처럼 활용할 수 있다.
fn main() {
let mut list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
let mut borrows_mutably = || list.push(7);
borrows_mutably();
println!("After calling closure: {:?}", list);
}
이 경우 클로저는 가변 참조자를 캡처한다.
use std::thread;
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
thread::spawn(move || println!("From thread: {:?}", list))
.join()
.unwrap();
}
move
키워드는 클로저가 소유권을 갖도록 만든다.
위 코드에서는 새 스레드를 생성해 인수로 실행될 클로저를 제공한다. 클로저 본문은 불변 참조자만 필요하지만, 클로저 정의 앞부분에 move
를 넣어 list
가 이동되어야 함을 명시할 필요가 있다.
만일 메인 스레드가 list
의 소유권을 유지하고 있는데 새 스레드가 끝나기 전에 끝나버려서 list
를 제거한다면, 새 스레드의 불변 참조자는 유효하지 않게 될 것이므로 list
를 새 스레드에 제공될 클로저로 이동시켜 참조자가 유효하도록 요구한다.
Fn 트레이트
클로저는 클로저 본문이 값을 처리하는 방식에 따라서 Fn
트레이트들을 자동으로 구현한다.
다음은 unwrap_or_else
메서드의 정의이다.
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}
T
는 Option
의 Some
배리언트 내 값의 타입을 나타내는 제네릭 타입이다.
unwrap_or_else
함수는 추가로 제네릭 변수 F
를 갖고 있다. F
는 f
라는 이름의 매개변수의 타입이다. 이것이 unwrap_or_else
를 호출할 때 제공하는 클로저이다.
Fnonce() -> T
는 트레이트 바운드로, F가 한 번만 호출될 수 있고, 인수가 없고, T를 반환함을 나타낸다.
FnOnce
는 한 번만 호출될 수 있는 클로저에 적용된다.FnMut
는 본문 밖으로 캡처된 값을 이동시키지는 않지만 값을 변경할 수는 있는 클로저에 대해 적용된다.Fn
은 캡처된 값을 본문 밖으로 이동시키지 않고 캡처된 값을 변경하지도 않는 클로저와, 환경으로부터 아무런 값도 캡처하지 않는 클로저에 적용된다.
'프로그래밍 > Rust' 카테고리의 다른 글
[Rust] 스마트 포인터 (0) | 2024.05.19 |
---|---|
[Rust] 반복자 (0) | 2024.05.19 |
[Rust] 테스트 (0) | 2024.05.19 |
[Rust] 제네릭과 트레이트 (0) | 2024.05.19 |
[Rust] 오류 처리 (0) | 2024.05.18 |