함수형 스타일의 프로그래밍
은 대개 아래의 것들을 포함 (아래의 것들을 closure
라고 한다.)함수를 값처럼 인수로 넘기는 것
, 다른 함수들에서 결괏값으로 함수들을 반환하는 것
, 나중에 실행하기 위해 함수를 변수에 할당하는 것
#[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
);
}
Option<T>
의 unwrap_or_else 메서드Option<T>
의 Some 배리언트에 저장되는 타입과 동일하며, 지금의 경우 ShirtColor입니다.self Inventory 인스턴스의 불변 참조자
를 캡처하여 우리가 지정한 코드와 함께 이 값을 unwrap_or_else 메서드에 넘겨줍니다. let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
세 가지 방식으로 자신의 환경으로부터 값을 캡처
할 수 있는데, 이는 함수가 매개변수를 취하는 세 가지 방식
과 직접적으로 대응됩니다: 캡처된 값이 쓰이는 방식에 기초
하여 캡처할 방법을 결정
매개변수 리스트 전에 move 키워드를 사용
할 수 있습니다.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();
}
시작 단계에서부터 환경으로부터 아무 값도 캡처하지 않기
(1)캡처된 값을 이동시키지도 변형시키지도 않기
(2)캡처된 값을 변형하기
(3)캡처된 값을 클로저 밖으로 이동시키기
(4)클로저가 환경으로부터 값을 캡처하고 다루는 방식(위 4가지)
은 이 클로저가 구현하는 트레이트에 영향
을 줌클로저가 구현한 트레이트
는 함수와 구조체가 사용할 수 있는 클로저의 종류를 명시할 수 있는 방법
FnOnce
캡처된 값을 본문 밖으로 이동시키는 클로저(4)
에 대해서는 FnOnce만 구현되며 나머지 Fn 트레이트는 구현되지 않는데, FnMut
본문 밖으로 캡처된 값을 이동시키지는 않지만, 값을 변경할 수는 있는 클로저(3)
에 대해 적용Fn
캡처된 값을 본문 밖으로 이동시키지 않고 캡처된 값을 변경하지도 않는 클로저(2)
는 물론, 환경으로부터 아무런 값도 캡처하지 않는 클로저(1)
에 적용 환경으로부터 값을 캡처할 필요가 없다면
, Fn 트레이트 중 하나를 구현한 무언가가 필요한 곳에 클로저 대신 함수 이름을 사용할 수 있습니다. Option<Vec<T>>
의 값 상에서 unwrap_or_else(Vec::new
)를 호출하여 이 값이 None일 경우 비어있는 새 벡터를 얻을 수 있습니다.원본 데이터의 소유권을 가져오지 않고, 단지 그 데이터를 참조만 합니다.
&[T]
또는 &mut [T]
형태로 불변 또는 가변 참조로 표현됩니다.현재 아이템에 대한 참조자(Rectangle)
를 하나의 인수로 받아서, 순서를 매길 수 있는 K 타입의 값을 반환합니다. #[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
list.sort_by_key(|r| r.width);
println!("{:#?}", list);
}
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
let mut sort_operations = vec![];
let value = String::from("by key called");
list.sort_by_key(|r| {
sort_operations.push(value);
r.width
});
println!("{:#?}", list);
}
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
v1_iter
)는 표준 라이브러리에 정의된 Iterator라는 이름의 트레이트
를 구현 let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
into_iter
를 호출 iter_mut
을 호출sum 메서드
가 있는데, 반복자의 소유권을 가져온 다음
반복적으로 next를 호출하는 방식으로 순회하며, 따라서 반복자를 소비 #[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
반복자 어댑터 메서드인 map
을 호출하는 예를 보여주는데, 클로저를 인수로 받아서 각 아이템에 대해 호출하여 아이템 전체를 순회합니다. v1.iter().map(|x| x + 1)
) 를 만듭니다:v1.iter().map(|x| x + 1)
)를 소비하기 위해서 collect 메서드를 사용할 것인데, let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
자신의 환경을 캡처하는 클로저
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
args[1]
) 대신 반복자의 소유권을 갖도록 build 함수를 변경할 수 있습니다. fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::build(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --생략--
}
fn main() {
let config = Config::build(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --생략--
}
impl Config {
pub fn build(
mut args: impl Iterator<Item = String>,
) -> Result<Config, &'static str> {
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let file_path = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file path"),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
반복자 스타일을 선호
합니다. 고수준의 추상화
지만, 컴파일되면 대략 직접 작성한 저수준의 코드와 같은 코드 수준으로 내려갑니다
.fn sum_array(arr: &[i32]) -> i32 {
let mut sum = 0;
for i in 0..arr.len() {
sum += arr[i];
}
sum
}
fn main() {
let array = [1, 2, 3, 4, 5, 6, 7, 8];
println!("Sum: {}", sum_array(&array));
}
이 코드는 배열의 요소를 순차적으로 더하는 단순한 루프입니다. for
반복문은 arr
의 길이만큼 실행되며, 각 반복마다 배열의 요소를 더하게 됩니다.
fn sum_array(arr: &[i32]) -> i32 {
let mut sum = 0;
let len = arr.len();
let mut i = 0;
// 루프 언롤링을 적용한 부분
while i + 3 < len {
sum += arr[i] + arr[i + 1] + arr[i + 2] + arr[i + 3];
i += 4;
}
// 남은 요소를 처리하는 루프
while i < len {
sum += arr[i];
i += 1;
}
sum
}
while
루프는 한 번에 4개의 요소를 더하게 됩니다.