Rust 프로그래밍 9장 - 클로저&람다 이야기
이 튜토리얼 시리즈는 다른 언어 하나 정도는 이미 정통하고 있는 사람들을 기반으로 합니다.
그게 Modern C++이면 더 할 나위 없이 좋으나, 자바나 다른 언어라도 별 무리는 없을 거 같습니다.(라이프타임이나 스마트포인터 같은거만 조금 더 공부해 보면 될 듯)
--
1장. 클로저의 기초
클로저는 익명 함수로, 컨텍스트에서 변수를 캡처할 수 있는 함수입니다.
일반적인 함수와 달리 클로저는 선언된 위치의 범위 내 데이터를 캡처합니다.
fn main() {
let x = 10;
let add_to_x = |y| x + y; // 클로저 정의 (익명 함수)
println!("Result: {}", add_to_x(5)); // 클로저 호출
}
핵심 포인트:
|y|는 파라미터를 정의하는 구문.클로저는
x를 캡처하여 사용.클로저는 함수처럼 호출 가능.
2장. 클로저와 함수의 차이점
클로저와 함수의 가장 큰 차이점은 클로저가 환경의 변수를 캡처할 수 있다는 점입니다.
함수는 환경 변수를 캡처하지 않음
fn add_to_x_fn(y: i32, x: i32) -> i32 {
x + y
}
fn main() {
let x = 10;
println!("Result: {}", add_to_x_fn(5, x)); // x를 직접 전달
}
클로저는 환경 변수를 자동으로 캡처
fn main() {
let x = 10;
let add_to_x = |y| x + y; // x가 이미 들어가 있죠? 이게 캡쳐입니다.
println!("Result: {}", add_to_x(5));
}핵심 포인트:
함수는 외부 변수를 참조할 수 없으며, 명시적으로 전달해야 합니다.
클로저는 선언된 환경의 변수에 직접 접근 가능합니다.
3장. 클로저와 가변 환경 캡처
클로저는 변수의 읽기, 쓰기, 소유권을 캡처하는 방식에 따라 다르게 동작합니다.
그 놈의 참조,소유권,Immutability ...
읽기 전용 캡처 (&T)
struct Counter {
value: i32,
}
fn main() {
let counter = Counter { value: 10 };
// 읽기 전용 캡처
let print_value = || println!("Counter value: {}", counter.value);
// 클로저 실행 (읽기 전용이므로 안전)
print_value();
print_value(); // 여러 번 실행 가능
// counter는 여전히 사용 가능
println!("Counter is still accessible: {}", counter.value);
}가변 참조 캡처 (&mut T)
struct Counter {
value: i32,
}
fn main() {
let mut counter = Counter { value: 10 };
// 가변 참조 캡처
let mut increment = || {
counter.value += 1; // counter.value를 수정
println!("Incremented value: {}", counter.value);
};
// 클로저 실행 (값 수정)
increment();
increment();
// counter는 클로저 이후에도 사용 가능
println!("Final Counter value: {}", counter.value);
}
값 소유 캡처 (T)
fn main() {
let movable = Box::new(3);
let consume = move || {
println!("movable: {:?}", movable);
};
consume();
println!("{:?}", movable); // error movable 소유권이 클로저 안으로 이동함.
}
////////////////////////////////////////////
struct Counter {
value: i32,
}
fn main() {
let counter = Counter { value: 10 };
let print_value = move || println!("Counter value: {}", counter.value);
print_value();
print_value(); // 여러 번 실행 가능
println!("Counter is still accessible: {}", counter.value); // move 여도 counter는 여전히 사용 가능
}핵심 포인트:
Rust는 캡처 방법을 자동으로 선택.
필요하면
move키워드로 소유권을 강제로 이동.move를 해도 스택에서 복사가 된 값은 여전히 사용 가능하다.
4장. 스택과 힙의 차이
#[derive(Debug)]
struct Counter {
value: i32,
}
fn main() {
let stack_value = Counter { value: 10 };
let heap_value = Box::new( Counter { value: 20 });
// 읽기 전용 캡처
let read_only_closure = || {
println!("Stack: {:?}, Heap: {:?}", stack_value, heap_value);
};
read_only_closure(); // 실행 가능
println!("Stack is still accessible: {:?}", stack_value);
println!("heap is still accessible: {:?}", heap_value);
// 소유권 이전 캡처
let move_closure = move || {
println!("Stack moved: {:?}", stack_value);
println!("Heap moved: {:?}", heap_value);
};
move_closure(); // 실행 가능
println!("stack is still accessible: {:?}", stack_value); // error
println!("Heap is still accessible: {:?}", heap_value); // error
}
위에서 좀 미묘한 부분인데 move로 소유권 이동시...상황에 따라 소유권이 이동 될 수도, 안될 수도 있다.
stack_value는 에러이지만
stack_value.value로 내부의 primitive 값을 캡쳐하면 괜찮다.
근데
heap_value 는 에러이면서
heap_value.value 도 에러이다.
아마도 스택에서 구조체 내부의 primitive 는 자동 복사가 되기 때문일 듯 하다.
즉 내부 필드가 Copy 트레이트를 구현하면, value 필드는 복사되고 외부에서 접근 가능하다는 의미로
Copy와 Clone에 대해서는 다음 장에서 배워보자.
5장. 클로저와 고차 함수
설명: 클로저는 Rust의 고차 함수(map, filter, fold 등)에서 자주 사용됩니다.
map과 filter
#[derive(Debug)]
struct Counter {
value: i32,
}
fn main() {
let nums = vec![1, 2, 3, 4];
let doubled: Vec<_> = nums.iter().map(|&x| x * 2).collect();
println!("{:?}", doubled); // Output: [2, 4, 6, 8]
let evens: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect();
println!("{:?}", evens); // Output: [2, 4]
println!("{:?}", nums); // Output: [1, 2, 3, 4]
}map과filter차이점:map은 각 원소에 대해 새로운 값을 반환합니다.filter는 조건을 만족하는 원소를 필터링하는 기능을 제공하는 반면,map은 변환을 목적으로 합니다.
iter():iter()는 벡터의 각 원소에 대한 불변 참조를 반환합니다. 즉,nums.iter()는&i32타입의 값을 하나씩 반환합니다. 예를 들어,nums가vec![1, 2, 3, 4]일 때,iter()는&1,&2,&3,&4와 같은 값을 반환합니다. 보통 자동으로 역참조되기 때문에 *x 를 통해 역참조 하지 않아도 map 안에서 그대로 사용 할 수 있습니다.filter는 &&x 이고 map은 &x 로 받는다. (둘 다 & 하나씩 빼도 작동을 잘 한다. 해당 레퍼런스는 자동 역참조 되니까)
진짜 코틀린 & 파이썬 & 자바스크립트 비교해보면 더 없이 복잡하다..
iter에 대해서는 다음 컬렉션장에서 배워보자.
6장. 클로저의 제네릭
fn apply_closure<F>(f: F) where F: Fn(i32) -> i32, {
println!("Result: {}", f(10));
}
fn main() {
let closure = |x| x * 2;
apply_closure(closure); // Output: 20
}다음 차례)
- Copy와 Clone 의 차이
- 컬렉션과 이터레이터
- 제네릭이란?
- Option 과 Result 및 예외처리
- 멀티쓰레드에 대하여
- select, channel 알아보기
- Tokio를 통한 비동기 예제 보기
- Custome Future 및 Runtime 만들어 보기
- 성능최적화 기법