Closure

Closure

날짜
생성자
ShalomShalom
카테고리
rust
작성일
2023년 05월 15일
태그
rust
이전 블로그

클로저(Closure)

Closure 정의 및 특징
rust 의 클로저는 변수에 저장하거나 다른 함수에 인수로 전달하는 익명 함수(anonymous functions)다.
  1. 코드 재사용
  1. 중복 호출 제거
  1. 함수와 달리 자신이 정의된 범위 내의 값들을 "캡처(Capture)" 한다
Function 예제
  • function 을 활용한 간단한 프로그램
    • 사고의 흐름대로 필요할 때 마다 필요한 Function을 수행한다
use std::thread; use std::time::Duration; fn main() { let simulated_user_specified_value = 10; let simulated_random_number = 7; generate_workout( simulated_user_specified_value, simulated_random_number ); } fn simulated_expensive_calculation(intensity:u32) -> u32 { println!("simulated_expensive_calculation function call!"); thread::sleep(Duration::from_secs(2)); intensity } fn generate_workout(intensity: u32, random_number: u32) { if intensity < 25 { println!( "Today, do {} pushups!", simulated_expensive_calculation(intensity) ); // Function 실행 1 println!( "Next, do {} situps!", simulated_expensive_calculation(intensity) ); // Function 실행 2 } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", simulated_expensive_calculation(intensity) ); // Function 실행 1 } } }
  • function 을 이용한 리팩토링
    • generate_workout()에 비해 중복적으로 호출하는 부분이 없어짐
    • 최대 2회 호출되던 코드에서 1회만 호출하도록 됨
fn generate_workout_ver1(intensity: u32, random_number: u32) { let expensive_result = simulated_expensive_calculation(intensity); // Function 실행 1 if intensity < 25 { println!( "Today, do {} pushups!", expensive_result ); println!( "Next, do {} situps!", expensive_result ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_result ); } } }
  • 실행 결과
generate_workout function test simulated_expensive_calculation function call! Today, do 10 pushups! simulated_expensive_calculation function call! Next, do 10 situps! generate_workout_ver1 function test simulated_expensive_calculation function call! Today, do 10 pushups! Next, do 10 situps!
클로져 활용
  • 리팩토링이라고 하기는 애매한 코드
    • 단순히 Function을 closure 로 변경했음
      • 위의 예제는 function 호출했고 closure은 호출할때 함
    • 계산은 한번 하고 필요한 부분에서 실행 하도록 함
      • 그러나 여전히 코드는 2번 closure 실행해서 시간은 동일?함
fn generate_workout_ver2(intensity: u32, random_number: u32) { // closures let expensive_closure = |num| { println!("expensive_closure_call?? capture??"); thread::sleep(Duration::from_secs(2)); num }; // 계산은 1회 if intensity < 25 { println!( "Today, do {} pushups!", expensive_closure(intensity) // 호출 1 ); println!( "Next, do {} situps!", expensive_closure(intensity) // 호출 2 ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_closure(intensity) ); } } }
  • 직관적이 해결 책은 closure 를 호출하고 반환되는 값을 사용하는 법
    • 우선 이렇게 하면 expensive_closure 호출이 줄어서 속도 빨라짐
let closure_return_value = expensive_closure(intensity); if intensity < 25 { println!( "Today, do {} pushups!", closure_return_value ); println!( "Next, do {} situps!", closure_return_value );
  • 메모이제이션(Memoization) 또는 지연 평가(lazy evaluation) 로 리팩토링
// where 절을 이용한 trait 경계 설정 struct Cacher<T> where T: Fn(u32) -> u32 // T 타입을 Closure Fn 형식으로 u32 를 불변으로 빌려온다라는 것으로 보임 { calculation: T, value: Option<u32>, } impl<T> Cacher<T> where T: Fn(u32) -> u32 { // 제네릭 파라미터T fn new(calculation: T) -> Cacher<T> { Cacher { calculation, value: None, } } fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.calculation)(arg); self.value = Some(v); v }, } } } fn generate_workout_ver3(intensity: u32, random_number: u32) { let mut expensive_result = Cacher::new(|num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }); if intensity < 25 { println!( "Today, do {} pushups!", expensive_result.value(intensity) // value 메소드를 호출하고 expensive_result로 반환된 Cacher struct option // 열거자로 선언된 value 값이None 이면 closure 를 실행하고 ); println!( "Next, do {} situps!", expensive_result.value(intensity) // 이미 위에서 closure 를 선언했기 때문에 None 이 아니면 value 값을 반환 ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_result.value(intensity) ); } } }
Closure 다양한 표현 및 제약
  • Type annotation
fn add_one_v1 (x: u32) -> u32 { x + 1 } // fn let add_one_v2 = |x: u32| -> u32 { x + 1 }; // Type 모두 명시 let add_one_v3 = |x| { x + 1 }; // Type annotaion 삭제 let add_one_v4 = |x| x + 1 ; // 표현식이 1개라 {} 삭제
  • 다른 Type으로 Closure 를 호출할 경우
let example_closure = |x| x; let s = example_closure(String::from("hello")); let n = example_closure(5); //--------------------------------------------------------------- // 당연한 이야기지만 두번째 closure 호출할 때는 String type 이여만 된다 // 아래와 같은 Error 발생 //--------------------------------------------------------------- error[E0308]: mismatched types --> src/main.rs | | let n = example_closure(5); | ^ expected struct `std::string::String`, found integral variable | = note: expected type `std::string::String` found type `{integer}`
Closure 로 환경 캡처
  • 캡처 방식은 세가지가 있음
    • FnOnce
      • 캡처한 변수들을 소비함.
    • Fn
      • 환경으로 부터 값들을 불변으로 빌려 옴
    • FnMut
      • 환경으로 부터 값들을 가변으로 빌려오기 때문에 환경을 변경할 수 있음
fn main() { let x = 4; // z가 호출 될때 y 값을 받고 캡처되어 유효한 y와 x가 비교가 됨 // Fn 캡처 방식 let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y)); } fn main() { let x = 4; // fn 에서 y를 받아서 z==x를 비교 했지만 x가 유효하지 않다 fn equal_to_x(z: i32) -> bool { z == x } // z 가 y를 let y = 4; assert!(equal_to_x(y)); } ----------------------------------------------------- error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead --> src/main.rs | 4 | fn equal_to_x(z: i32) -> bool { z == x } | fn main() { let x = vec![1, 2, 3]; // move 키워드로 인하여 x 가 closure 내로 이동하고 소유권을 넘겨 받음 let equal_to_x = move |z| z == x; // x에 대한 소유권이 closure에 있기때문에 에러가 발생 println!("can't use x here: {:?}", x); let y = vec![1, 2, 3]; assert!(equal_to_x(y)); } ----------------------------------------------------- error[E0382]: use of moved value: `x` --> src/main.rs:6:40 | 4 | let equal_to_x = move |z| z == x; | -------- value moved (into closure) here 5 | 6 | println!("can't use x here: {:?}", x); | ^ value used here after move | = note: move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

Iterator

  1. 일련의 아이템을 대상으로 차례대로 수행
  1. 실제로 사용하는 메서드를 호출하기 전까지 아무런 일이 발생하지 않는 지연(lazy) 특성이 있다
for loop에서 반복자 사용의 예
  • 간단하게 v1_iter의 마지막 항목이 될때까지 반복
    • 내부적으로 next() 로 반복자를 소진한다.
let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("Got: {}", val); }
Iterator trait 와 next method
  • iter
    • v1 벡터의 값을 불변한 참조 반환
  • into_iter
    • v1 벡터의 값의 소유권을 갖고 반환
    • 언제 쓰일까? 반환 된 값에 대하여 외부에서 읽고 쓰지 못하도록?? 또는 소유해야 읽고 쓸 수 있으니깐?
  • iter_mut
    • v1 벡터의 값을 가변 참조로 반환
    • 너도 언제 쓰이니? 읽고 바로 수정해서 넣어야 하는.. 간단한 r/w 데이터 저장소 구현??
fn iterator_demonstration() { let v1 = vec![1, 2, 3]; // .iter()를 호출하면서 내부 상태 변경하기때문에 mut 로 선언해야한다는 사실 기억 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()를 호출하는 메서드를 이야기 하고 "소비하는 어댑터(consuming adapter)" 라고 이야기
#[test] fn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum(); // sum 메서드는 소유권을 갖기때문에 이후에는 v1_iter를 사용하지 못함 assert_eq!(total, 6); }
다른 반복자를 생성하는 메서드들
  • 반복자 어댑터(iterator adaptors)
let v1: Vec<i32> = vec![1, 2, 3]; // collect() 를 해야 map 메서드에 입력으로 받은 |x| = x + 1의 클로저를 실행하고 // 반복자를 소비한 후에 반복자를 생성해서 v2 로 반환함 let v2: Vec<_> = v1.iter() .map(|x| x + 1) .collect(); assert_eq!(v2, vec![2, 3, 4]);
환경을 캡쳐하는 클로저 사용하기
  • filter 반복자 어댑터를 사용
  • 반복자로 부터 각 항목을 받아 Boolean 을 반환하는 클로저를 인자로 받음
    • true는 포함
    • false는 제외
struct Shoe { size: u32, style: String, } fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> { shoes.into_iter() // 소유권을 갖고와야 외부에 있던 shoes 벡터 값을 읽을수 있다? 맞나? .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") }, ] ); }
Iterator 트레잇으로 자신만의 반복자 만들기
 

댓글

guest