- 발표: 김기덕
- 리뷰: 정연집
- 서기: 이재현
정연집 님 : 코드리뷰
- 함수형 프로그래밍에 많은 영향을 받음
- 클로저
- 반복자
- 12장 I/O 프로젝트 향상 및 성능 체크
- 익명 함수 (C++의 람다함수)
- 함수를 클로저로 변경
fn simulated_expensive_calculation(intensity: u32) -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
intensity
}
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
- 클로저는 fn 함수처럼 파라미터나 반환값의 타입을 명시할 것을 요구하지 않습니다.
- 클로저는 보통 짧고 임의의 시나리오 보다 좁은 문맥 안에서만 관련이 있어서 컴파일러가 안정적으로 추론할 수 있다.
// 모두 동일
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 ;
- 메모리제이션(지연평가): 결과값을 캐시에 저장
struct Cacher<T>
where T: Fn(u32) -> u32 // 구조체에는 클로저 타입을 명시해주어야함.
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where T: Fn(u32) -> u32
{
fn new(calculation: T) -> Cacher<T> { // 데이터를 생성
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
// 데이터를 얻기 (이미 있으면 그냥 반환, 없으면 calculation에 저장된 클로저로 생성)
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
-
Cacher 인스턴스가 value 메소드의 arg 파라미터에 대해 항상 같은 값을 얻는다. (해시맵을 사용하자.)
-
u32 타입 파라미터 한 개만 받고 하나의 u32 을 반환한다 (더 중립적인 파라미터를 사용하자.)
- 클로저로 변수 참조.
fn main() {
let x = 4;
let equal_to_x = |z| z == x; // 상위 스코프 변수x를 캡쳐하여 사용
let y = 4;
assert!(equal_to_x(y));
}
- 함수로 변수 참조(X)
fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool { z == x } // x를 캡쳐할 수 없어서 에러 발생
let y = 4;
assert!(equal_to_x(y));
}
FnOnce
캡처한 변수의 소유권을 가져갑니다, 그래서 한 번만 호출 될 수 있습니다.Fn
은 그 환경으로 부터 값들을 불변으로 빌려 옵니다.FnMut
값들을 가변으로 빌려오기 때문에 그 환경을 변경할 수 있습니다.
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x; // 'move'키워드를 통해 x의 소유권을 클로저에게 넘김
println!("can't use x here: {:?}", x); // x의 소유권을 클로저가 가져서 컴파일 불가
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
- 일련의 항목들에 대해 순서대로 어떤 작업을 수행할 수 있도록 해줍니다.
- 반복자는 게으른데, 항목들을 사용하기위해 반복자를 소비하는 메서드를 호출해야 합니다.
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter(); // 반복자를 생성하지만 아무일도 일어나지 않는다.
- for 루프에서 반복자 사용
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
- 모든 반복자는 표준 라이브러리에 정의된 Iterator 라는 이름의 트레잇을 구현 합니다.
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}
- 주의사항 -'next' 호출로 얻어온 값들은 벡터 안에 있는 값들에 대한 불변 참조합니다.
- next 메서드를 구현해야 반복자를 소비하는 메서드를 사용할 수 있다.
iter() // 불변 참조
into_iter() // 소유권 이전
iter_mut() // 가변 참조
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1); //클로저를 인자로 전달.
- 반복자를 생성해도 반복자는 게으르므로 소비를 해주어야한다.
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // collect()를 사용해 반복자를 소비
assert_eq!(v2, vec![2, 3, 4]);
filter()
: iterator로부터 각항목을 받아 boolean을 반환하는 클로저를 인자로 전달받는다.
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter()
.filter(|s| s.size == shoe_size)
.collect()
}
#[test]
fn filters_by_size() {
let shoes = vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 13, style: String::from("sandal") },
Shoe { size: 10, style: String::from("boot") },
];
let in_my_size = shoes_in_my_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 10, style: String::from("boot") },
]
);
}
-
next 메서드를 정의해 자신만의 반복자를 생성할 수 있다.
-
Counter 반복자 생성하기
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
- Counter 반복자 사용하기
#[test]
fn calling_next_directly() {
let mut counter = Counter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);
}
- Iterator 메서드 사용 가능: Interator의 next메소드를 구현했기때문문이다.
#[test]
fn using_other_iterator_trait_methods() {
let sum: u32 = Counter::new().zip(Counter::new().skip(1)) // Counter 인스턴스에 의해 생성된 값과 쌍을 이루며
.map(|(a, b)| a * b) // 각 쌍을 함께 곱하고,
.filter(|x| x % 3 == 0) // 3으로 나눠지는 값들만 유지하며
.sum(); // 모든 결과 값을 함께 더한다
assert_eq!(18, sum);
}
- 기존 코드
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> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
- 속도 -Iterator가 빠름...
- 이지한: 0
- 김기덕: 0
- 유병조: 0
- 이명수: 0
- 이재현: 0
- 정연집: 0
- 허신: 10,000
- 이지한: 결석
- 김기덕: 뒤로 갈수록 어려워지는 러스트네요...!
- 유병조: 함수형 기능들이 지원되서 편리하네요~
- 이명수: 하루 하루 기억력이... ㅎㅎ
- 이재현: 오늘 챕터는 꽤 재밌었어요, 직접 만나서 스터디 하니까 다들 이야기도 좀 더 많이하고 좋았어요
- 정연집: 처음으로 직접보고 스터디해서 좋았습니다~
- 허신: 파이썬과 비슷한 부분이 많아서 익숙하고 재밌었어요~