클로저는 함수형 프로그래밍의 핵심이자 가장 우아한 개념 중 하나입니다. 토파즈에서 클로저의 마법을 경험해보세요! ✨
🌱 클로저의 기본 개념
클로저란 무엇인가?
클로저는 자신이 정의된 환경의 변수들을 기억하고 접근할 수 있는 함수입니다. 함수가 생성될 때의 환경(스코프)을 "닫아서(close)" 가지고 다니기 때문에 클로저라고 불립니다.
// 기본 클로저 예제
function 외부함수(외부변수: int) -> impl Fn(int) -> int {
// 클로저 생성 - 외부변수를 캡처함
let 내부함수 = |내부변수: int| -> int {
외부변수 + 내부변수 // 외부 스코프의 변수에 접근
}
return 내부함수
}
// 클로저 카운터 예제
function 카운터생성기(초기값: int) -> impl Fn() -> int {
let mut 현재값 = 초기값
// 현재값을 캡처하는 클로저
return move || -> int {
현재값 += 1
현재값
}
}
// 곱하기 함수 생성기
function 곱하기생성기(배수: int) -> impl Fn(int) -> int {
|값: int| 값 * 배수
} test {
// 외부함수 테스트
let 더하기5 = 외부함수(5)
assert 더하기5(3) == 8
assert 더하기5(10) == 15
// 카운터 테스트
let mut 카운터1 = 카운터생성기(0)
let mut 카운터2 = 카운터생성기(100)
assert 카운터1() == 1
assert 카운터1() == 2
assert 카운터2() == 101
assert 카운터1() == 3 // 독립적인 상태 유지
// 곱하기 생성기 테스트
let 두배만들기 = 곱하기생성기(2)
let 세배만들기 = 곱하기생성기(3)
assert 두배만들기(5) == 10
assert 세배만들기(4) == 12
}
print("더하기5(7) = {외부함수(5)(7)}")
let mut 내카운터 = 카운터생성기(10)
print("카운터: {내카운터()}, {내카운터()}, {내카운터()}")
let 열배만들기 = 곱하기생성기(10)
print("10 * 7 = {열배만들기(7)}")
클로저의 핵심 특징
- 환경 캡처: 외부 스코프의 변수들을 기억
- 상태 보존: 함수 호출 사이에 상태를 유지
- 지연 실행: 필요할 때까지 계산을 미룸
- 코드 재사용: 동일한 로직을 다양한 컨텍스트에서 활용
🎨 다양한 클로저 패턴
값 캡처 vs 참조 캡처
// 값 캡처 (Copy)
function 값캡처예제() -> impl Fn() -> int {
let 값 = 42
// 값을 복사해서 캡처
|| 값 // 값이 클로저 내부로 복사됨
}
// 참조 캡처 (Borrow)
function 참조캡처예제() -> impl Fn() -> int {
let 값 = 42
// 참조로 캡처 (수명이 허용하는 경우)
move || 값 // move 키워드로 소유권 이전
}
// 가변 참조 캡처
function 가변캡처예제() -> impl FnMut() -> int {
let mut 카운트 = 0
// 가변 참조로 캡처
move || -> int {
카운트 += 1
카운트
}
}
// 복잡한 환경 캡처
function 복잡한환경() -> (impl Fn(int) -> int, impl Fn(int) -> int) {
let 기본값 = 10
let 배수 = 3
let mut 누적값 = 0
let 더하기함수 = move |추가값: int| -> int {
누적값 += 추가값
기본값 + 누적값
}
let 곱하기함수 = |입력값: int| -> int {
입력값 * 배수
}
return (더하기함수, 곱하기함수)
}
// 클로저 체이닝
function 함수체인생성(초기함수: impl Fn(int) -> int) -> impl Fn(impl Fn(int) -> int) -> impl Fn(int) -> int {
move |다음함수: impl Fn(int) -> int| -> impl Fn(int) -> int {
move |입력: int| -> int {
다음함수(초기함수(입력))
}
}
} test {
// 값 캡처 테스트
let 값클로저 = 값캡처예제()
assert 값클로저() == 42
// 가변 캡처 테스트
let mut 가변클로저 = 가변캡처예제()
assert 가변클로저() == 1
assert 가변클로저() == 2
assert 가변클로저() == 3
// 복잡한 환경 테스트
let (mut 더하기, 곱하기) = 복잡한환경()
assert 더하기(5) == 15 // 10 + 5
assert 더하기(3) == 18 // 10 + 5 + 3
assert 곱하기(4) == 12 // 4 * 3
// 함수 체이닝 테스트
let 두배 = |x: int| x * 2
let 더하기10 = |x: int| x + 10
let 체인드함수 = 함수체인생성(두배)(더하기10)
assert 체인드함수(5) == 20 // (5 * 2) + 10
}
let mut 내가변클로저 = 가변캡처예제()
print("가변 클로저: {내가변클로저()}, {내가변클로저()}")
let (mut 더하기함수, 곱하기함수) = 복잡한환경()
print("더하기: {더하기함수(7)}, 곱하기: {곱하기함수(6)}")
고차 함수와 클로저
// 함수를 반환하는 함수
function 조건부함수생성기(조건: bool) -> impl Fn(int) -> int {
if 조건 {
|x: int| x * 2 // 두 배
} else {
|x: int| x + 10 // 10 더하기
}
}
// 함수를 매개변수로 받는 함수
function 함수적용기<F, T>(값: T, 함수: F) -> T
where
F: Fn(T) -> T
{
함수(값)
}
// 여러 함수를 조합하는 컴포지터
function 함수컴포지터<F, G, T>(함수1: F, 함수2: G) -> impl Fn(T) -> T
where
F: Fn(T) -> T,
G: Fn(T) -> T,
T: 'static
{
move |입력: T| -> T {
함수2(함수1(입력))
}
}
// 커링(Currying) 구현
function 커링예제(a: int) -> impl Fn(int) -> impl Fn(int) -> int {
move |b: int| -> impl Fn(int) -> int {
move |c: int| -> int {
a + b + c
}
}
}
// 메모이제이션 클로저
function 메모이제이션<F, T, U>(함수: F) -> impl FnMut(T) -> U
where
F: Fn(T) -> U,
T: Clone + std::hash::Hash + Eq,
U: Clone,
{
use std::collections::HashMap
let mut 캐시: HashMap<T, U> = HashMap::new()
move |입력: T| -> U {
match 캐시.get(&입력) { case Some(결과) => return 결과.clone(), case None => {} }
let 결과 = 함수(입력.clone())
캐시.insert(입력, 결과.clone())
결과
}
} test {
// 조건부 함수 생성기 테스트
let 두배함수 = 조건부함수생성기(true)
let 더하기함수 = 조건부함수생성기(false)
assert 두배함수(5) == 10
assert 더하기함수(5) == 15
// 함수 적용기 테스트
let 결과 = 함수적용기(10, |x| x * 3)
assert 결과 == 30
// 함수 컴포지터 테스트
let 더하기5 = |x: int| x + 5
let 곱하기2 = |x: int| x * 2
let 조합함수 = 함수컴포지터(더하기5, 곱하기2)
assert 조합함수(3) == 16 // (3 + 5) * 2
// 커링 테스트
let 커링함수 = 커링예제(1)(2)(3)
assert 커링함수 == 6 // 1 + 2 + 3
// 메모이제이션 테스트
let mut 메모팩토리얼 = 메모이제이션(|n: int| -> int {
if n <= 1 { 1 } else { n * 메모팩토리얼(n - 1) }
})
assert 메모팩토리얼(5) == 120
}
print("조건부(true): {조건부함수생성기(true)(7)}")
print("조합함수 결과: {함수컴포지터(|x| x + 1, |x| x * 10)(4)}")
print("커링 결과: {커링예제(10)(20)(30)}")
이벤트 처리와 콜백
// 이벤트 핸들러 타입 정의
type 이벤트핸들러<T> = Box<dyn FnMut(T)>
// 간단한 이벤트 시스템
struct 이벤트시스템<T> {
핸들러들: Vec<이벤트핸들러<T>>
}
impl<T> 이벤트시스템<T>
where
T: Clone
{
function new() -> Self {
이벤트시스템 {
핸들러들: Vec::new()
}
}
// 이벤트 핸들러 등록
function 핸들러등록<F>(&mut self, 핸들러: F)
where
F: FnMut(T) + 'static
{
self.핸들러들.push(Box::new(핸들러))
}
// 이벤트 발생
function 이벤트발생(&mut self, 데이터: T) {
for 핸들러 in &mut self.핸들러들 {
핸들러(데이터.clone())
}
}
}
// 비동기 작업 시뮬레이션
function 비동기작업<F>(지연시간: int, 완료콜백: F)
where
F: FnOnce(string) + 'static
{
// 실제로는 비동기 처리가 되겠지만, 여기서는 즉시 실행
let 결과 = format!("작업 완료 ({}ms 후)", 지연시간)
완료콜백(결과)
}
// 상태 변화 감지기
struct 상태감지기<T> {
현재상태: T,
변화콜백들: Vec<Box<dyn Fn(&T, &T)>>
}
impl<T> 상태감지기<T>
where
T: Clone + PartialEq
{
function new(초기상태: T) -> Self {
상태감지기 {
현재상태: 초기상태,
변화콜백들: Vec::new()
}
}
function 변화감지등록<F>(&mut self, 콜백: F)
where
F: Fn(&T, &T) + 'static
{
self.변화콜백들.push(Box::new(콜백))
}
function 상태변경(&mut self, 새상태: T) {
if self.현재상태 != 새상태 {
let 이전상태 = self.현재상태.clone()
self.현재상태 = 새상태.clone()
for 콜백 in &self.변화콜백들 {
콜백(&이전상태, &새상태)
}
}
}
function 현재값(&self) -> &T {
&self.현재상태
}
} test {
// 이벤트 시스템 테스트
let mut 이벤트시스템 = 이벤트시스템::<string>::new()
let mut 받은메시지들: Vec<string> = Vec::new()
이벤트시스템.핸들러등록(move |메시지: string| {
받은메시지들.push(format!("핸들러1: {}", 메시지))
})
이벤트시스템.핸들러등록(|메시지: string| {
println!("핸들러2에서 받음: {}", 메시지)
})
이벤트시스템.이벤트발생("테스트 메시지".to_string())
// 상태 감지기 테스트
let mut 온도감지기 = 상태감지기::new(20.0)
let mut 변화기록: Vec<string> = Vec::new()
온도감지기.변화감지등록(move |이전: &f64, 새값: &f64| {
변화기록.push(format!("온도 변화: {}°C → {}°C", 이전, 새값))
})
온도감지기.상태변경(25.0)
온도감지기.상태변경(30.0)
assert *온도감지기.현재값() == 30.0
}
// 사용 예시
let mut 클릭이벤트 = 이벤트시스템::<string>::new()
클릭이벤트.핸들러등록(|버튼명: string| {
print!("버튼 '{}'이 클릭되었습니다!", 버튼명)
})
클릭이벤트.이벤트발생("확인".to_string())
// 비동기 작업 예시
비동기작업(1000, |결과: string| {
print!("비동기 작업 결과: {}", 결과)
})
🚀 실용적인 클로저 활용
함수형 프로그래밍 패턴
// 함수형 파이프라인
function 파이프라인<T>(초기값: T) -> 파이프라인빌더<T> {
파이프라인빌더::new(초기값)
}
struct 파이프라인빌더<T> {
값: T
}
impl<T> 파이프라인빌더<T> {
function new(값: T) -> Self {
파이프라인빌더 { 값 }
}
function map<U, F>(self, 함수: F) -> 파이프라인빌더<U>
where
F: FnOnce(T) -> U
{
파이프라인빌더::new(함수(self.값))
}
function filter<F>(self, 조건: F) -> Option<파이프라인빌더<T>>
where
F: FnOnce(&T) -> bool
{
if 조건(&self.값) {
Some(self)
} else {
None
}
}
function fold<U, F>(self, 초기값: U, 함수: F) -> U
where
F: FnOnce(U, T) -> U
{
함수(초기값, self.값)
}
function finish(self) -> T {
self.값
}
}
// 배열 처리 유틸리티
function 배열처리<T>() -> 배열처리기<T> {
배열처리기::new()
}
struct 배열처리기<T> {
_phantom: std::marker::PhantomData<T>
}
impl<T> 배열처리기<T> {
function new() -> Self {
배열처리기 { _phantom: std::marker::PhantomData }
}
function map<U, F>(&self, 배열: Vec<T>, 함수: F) -> Vec<U>
where
F: Fn(T) -> U
{
배열.into_iter().map(함수).collect()
}
function filter<F>(&self, 배열: Vec<T>, 조건: F) -> Vec<T>
where
F: Fn(&T) -> bool
{
배열.into_iter().filter(조건).collect()
}
function reduce<F>(&self, 배열: Vec<T>, 함수: F) -> Option<T>
where
F: Fn(T, T) -> T
{
배열.into_iter().reduce(함수)
}
function fold<U, F>(&self, 배열: Vec<T>, 초기값: U, 함수: F) -> U
where
F: Fn(U, T) -> U
{
배열.into_iter().fold(초기값, 함수)
}
}
// 조건부 실행 체인
function 만약<F>(조건: bool, 실행함수: F) -> 조건부실행<F>
where
F: FnOnce()
{
조건부실행 { 조건, 실행함수: Some(실행함수) }
}
struct 조건부실행<F>
where
F: FnOnce()
{
조건: bool,
실행함수: Option<F>
}
impl<F> 조건부실행<F>
where
F: FnOnce()
{
function 그렇지않으면<G>(mut self, 다른실행함수: G)
where
G: FnOnce()
{
if self.조건 {
match self.실행함수.take() { case Some(함수) => 함수(), case None => {} }
} else {
다른실행함수()
}
}
function 실행(mut self) {
if self.조건 {
match self.실행함수.take() { case Some(함수) => 함수(), case None => {} }
}
}
} test {
// 파이프라인 테스트
let 결과 = 파이프라인(10)
.map(|x| x * 2)
.map(|x| x + 5)
.filter(|&x| x > 20)
.map(|x| x.to_string())
.finish()
assert 결과 == Some("25".to_string())
// 배열 처리 테스트
let 배열처리기 = 배열처리::<i32>()
let 숫자들 = vec![1, 2, 3, 4, 5]
let 제곱들 = 배열처리기.map(숫자들.clone(), |x| x * x)
assert 제곱들 == vec![1, 4, 9, 16, 25]
let 짝수들 = 배열처리기.filter(숫자들.clone(), |&x| x % 2 == 0)
assert 짝수들 == vec![2, 4]
let 합계 = 배열처리기.fold(숫자들, 0, |acc, x| acc + x)
assert 합계 == 15
// 조건부 실행 테스트
let mut 실행됨 = false
만약(true, || {
실행됨 = true
}).실행()
assert 실행됨 == true
}
// 사용 예시
let 처리결과 = 파이프라인(vec![1, 2, 3, 4, 5])
.map(|배열| 배열처리::<i32>().map(배열, |x| x * 2))
.map(|배열| 배열처리::<i32>().filter(배열, |&x| x > 5))
.finish()
print!("처리된 배열: {:?}", 처리결과)
만약(처리결과.len() > 2, || {
print!("배열이 충분히 큽니다!")
}).그렇지않으면(|| {
print!("배열이 너무 작습니다.")
})
지연 평가와 스트림
// 지연 평가 시퀀스
struct 지연시퀀스<T, F>
where
F: Fn() -> Option<T>
{
생성함수: F
}
impl<T, F> 지연시퀀스<T, F>
where
F: Fn() -> Option<T>
{
function new(생성함수: F) -> Self {
지연시퀀스 { 생성함수 }
}
function take(self, 개수: usize) -> Vec<T> {
let mut 결과 = Vec::new()
for _ in 0..개수 {
match (self.생성함수)() { case Some(값) => 결과.push(값), case None => break }
}
결과
}
}
// 무한 수열 생성기
function 무한수열생성(시작: int, 간격: int) -> impl Fn() -> Option<int> {
let mut 현재 = 시작
move || -> Option<int> {
let 값 = 현재
현재 += 간격
Some(값)
}
}
// 피보나치 수열 생성기
function 피보나치생성기() -> impl Fn() -> Option<int> {
let mut a = 0
let mut b = 1
move || -> Option<int> {
let 현재 = a
let 다음 = a + b
a = b
b = 다음
Some(현재)
}
}
// 조건부 무한 수열
function 조건부수열<F, P>(생성함수: F, 조건: P) -> impl Fn() -> Option<int>
where
F: Fn() -> int,
P: Fn(int) -> bool
{
move || -> Option<int> {
loop {
let 값 = 생성함수()
if 조건(값) {
return Some(값)
}
}
}
}
// 스트림 처리기
struct 스트림<T> {
데이터: Vec<T>
}
impl<T> 스트림<T>
where
T: Clone
{
function from(데이터: Vec<T>) -> Self {
스트림 { 데이터 }
}
function map<U, F>(self, 함수: F) -> 스트림<U>
where
F: Fn(T) -> U
{
스트림 {
데이터: self.데이터.into_iter().map(함수).collect()
}
}
function filter<F>(self, 조건: F) -> 스트림<T>
where
F: Fn(&T) -> bool
{
스트림 {
데이터: self.데이터.into_iter().filter(조건).collect()
}
}
function take(self, 개수: usize) -> 스트림<T> {
스트림 {
데이터: self.데이터.into_iter().take(개수).collect()
}
}
function collect(self) -> Vec<T> {
self.데이터
}
} test {
// 무한 수열 테스트
let 등차수열 = 무한수열생성(1, 2) // 1, 3, 5, 7, ...
let 첫다섯개 = (0..5).map(|_| 등차수열()).collect::<Vec<_>>()
assert 첫다섯개 == vec![Some(1), Some(3), Some(5), Some(7), Some(9)]
// 피보나치 테스트
let 피보나치 = 피보나치생성기()
let 피보나치첫여섯개: Vec<_> = (0..6).map(|_| 피보나치()).collect()
assert 피보나치첫여섯개 == vec![Some(0), Some(1), Some(1), Some(2), Some(3), Some(5)]
// 스트림 처리 테스트
let 결과 = 스트림::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.filter(|&x| x % 2 == 0) // 짝수만
.map(|x| x * x) // 제곱
.take(3) // 처음 3개
.collect()
assert 결과 == vec![4, 16, 36] // 2², 4², 6²
}
// 사용 예시
let 홀수생성기 = 무한수열생성(1, 2)
print!("첫 10개 홀수: {:?}", (0..<10).map(|_| 홀수생성기()).collect::<Vec<_>>())
let 피보나치 = 피보나치생성기()
print!("피보나치 수열: {:?}", (0..8).map(|_| 피보나치()).collect::<Vec<_>>())
let 스트림결과 = 스트림::from((1..=20).collect())
.filter(|&x| x % 3 == 0) // 3의 배수
.map(|x| format!("{}번", x)) // 문자열 변환
.take(4)
.collect()
print!("3의 배수들: {:?}", 스트림결과)
🎯 고급 클로저 기법
동적 함수 생성
// 런타임 함수 생성기
enum 연산타입 {
더하기,
빼기,
곱하기,
나누기
}
function 연산함수생성(연산: 연산타입, 피연산자: f64) -> Box<dyn Fn(f64) -> f64> {
match 연산 {
case 연산타입::더하기 => Box::new(move |x| x + 피연산자),
case 연산타입::빼기 => Box::new(move |x| x - 피연산자),
case 연산타입::곱하기 => Box::new(move |x| x * 피연산자),
case 연산타입::나누기 => Box::new(move |x| {
if 피연산자 != 0.0 { x / 피연산자 } else { f64::NAN }
})
}
}
// 조건부 함수 체인
struct 함수체인<T> {
함수들: Vec<Box<dyn Fn(T) -> T>>
}
impl<T> 함수체인<T>
where
T: 'static
{
function new() -> Self {
함수체인 { 함수들: Vec::new() }
}
function 추가<F>(mut self, 함수: F) -> Self
where
F: Fn(T) -> T + 'static
{
self.함수들.push(Box::new(함수))
self
}
function 조건부추가<F>(mut self, 조건: bool, 함수: F) -> Self
where
F: Fn(T) -> T + 'static
{
if 조건 {
self.함수들.push(Box::new(함수))
}
self
}
function 실행(self, 초기값: T) -> T {
self.함수들.into_iter().fold(초기값, |값, 함수| 함수(값))
}
}
// 설정 가능한 검증기 생성
struct 검증규칙<T> {
이름: string,
검증함수: Box<dyn Fn(&T) -> bool>
}
struct 검증기<T> {
규칙들: Vec<검증규칙<T>>
}
impl<T> 검증기<T> {
function new() -> Self {
검증기 { 규칙들: Vec::new() }
}
function 규칙추가<F>(mut self, 이름: string, 검증함수: F) -> Self
where
F: Fn(&T) -> bool + 'static
{
self.규칙들.push(검증규칙 {
이름,
검증함수: Box::new(검증함수)
})
self
}
function 검증(&self, 값: &T) -> Result<(), Vec<string>> {
let mut 오류들 = Vec::new()
for 규칙 in &self.규칙들 {
if !(규칙.검증함수)(값) {
오류들.push(규칙.이름.clone())
}
}
if 오류들.is_empty() {
Ok(())
} else {
Err(오류들)
}
}
} test {
// 동적 함수 생성 테스트
let 더하기10 = 연산함수생성(연산타입::더하기, 10.0)
let 곱하기2 = 연산함수생성(연산타입::곱하기, 2.0)
assert 더하기10(5.0) == 15.0
assert 곱하기2(7.0) == 14.0
// 함수 체인 테스트
let 체인 = 함수체인::new()
.추가(|x: i32| x + 1)
.조건부추가(true, |x: i32| x * 2)
.추가(|x: i32| x - 3)
let 결과 = 체인.실행(5) // ((5 + 1) * 2) - 3 = 9
assert 결과 == 9
// 검증기 테스트
let 숫자검증기 = 검증기::new()
.규칙추가("양수여야함".to_string(), |&x: &i32| x > 0)
.규칙추가("100보다작아야함".to_string(), |&x: &i32| x < 100)
.규칙추가("짝수여야함".to_string(), |&x: &i32| x % 2 == 0)
assert 숫자검증기.검증(&50).is_ok()
assert 숫자검증기.검증(&-5).is_err()
assert 숫자검증기.검증(&51).is_err() // 홀수이므로 실패
}
// 사용 예시
let 계산체인 = 함수체인::new()
.추가(|x: f64| x * 1.5) // 50% 증가
.조건부추가(true, |x| x + 10.0) // 10 추가
.추가(|x| x.round()) // 반올림
let 최종결과 = 계산체인.실행(7.3)
print!("계산 결과: {}", 최종결과)
let 문자열검증 = 검증기::new()
.규칙추가("비어있지않음".to_string(), |s: &string| !s.is_empty())
.규칙추가("최소길이".to_string(), |s: &string| s.len() >= 3)
.규칙추가("특수문자포함".to_string(), |s: &string| s.contains('@'))
match 문자열검증.검증(&"test@example.com".to_string()) {
case Ok(()) => print!("검증 성공!"),
case Err(오류들) => print!("검증 실패: {:?}", 오류들)
}
플러그인 시스템
// 플러그인 인터페이스
trait 플러그인 {
function 이름(&self) -> &str
function 실행(&self, 입력: &str) -> string
}
// 클로저 기반 플러그인
struct 클로저플러그인<F>
where
F: Fn(&str) -> string
{
이름: string,
실행함수: F
}
impl<F> 클로저플러그인<F>
where
F: Fn(&str) -> string
{
function new(이름: string, 실행함수: F) -> Self {
클로저플러그인 { 이름, 실행함수 }
}
}
impl<F> 플러그인 for 클로저플러그인<F>
where
F: Fn(&str) -> string
{
function 이름(&self) -> &str {
&self.이름
}
function 실행(&self, 입력: &str) -> string {
(self.실행함수)(입력)
}
}
// 플러그인 매니저
struct 플러그인매니저 {
플러그인들: Vec<Box<dyn 플러그인>>
}
impl 플러그인매니저 {
function new() -> Self {
플러그인매니저 { 플러그인들: Vec::new() }
}
function 플러그인등록<P>(mut self, 플러그인: P) -> Self
where
P: 플러그인 + 'static
{
self.플러그인들.push(Box::new(플러그인))
self
}
function 모든플러그인실행(&self, 입력: &str) -> Vec<(string, string)> {
self.플러그인들
.iter()
.map(|플러그인| (플러그인.이름().to_string(), 플러그인.실행(입력)))
.collect()
}
function 플러그인찾기(&self, 이름: &str) -> Option<&dyn 플러그인> {
self.플러그인들
.iter()
.find(|플러그인| 플러그인.이름() == 이름)
.map(|플러그인| 플러그인.as_ref())
}
}
// 미들웨어 시스템
type 미들웨어<T> = Box<dyn Fn(T, Box<dyn Fn(T) -> T>) -> T>
struct 미들웨어체인<T> {
미들웨어들: Vec<미들웨어<T>>
}
impl<T> 미들웨어체인<T>
where
T: 'static
{
function new() -> Self {
미들웨어체인 { 미들웨어들: Vec::new() }
}
function 미들웨어추가<F>(mut self, 미들웨어: F) -> Self
where
F: Fn(T, Box<dyn Fn(T) -> T>) -> T + 'static
{
self.미들웨어들.push(Box::new(미들웨어))
self
}
function 실행<F>(self, 입력: T, 최종핸들러: F) -> T
where
F: Fn(T) -> T + 'static
{
let mut 체인 = Box::new(최종핸들러) as Box<dyn Fn(T) -> T>
for 미들웨어 in self.미들웨어들.into_iter().rev() {
let 이전체인 = 체인
체인 = Box::new(move |입력| 미들웨어(입력, 이전체인))
}
체인(입력)
}
} test {
// 플러그인 시스템 테스트
let 매니저 = 플러그인매니저::new()
.플러그인등록(클로저플러그인::new(
"대문자변환".to_string(),
|텍스트| 텍스트.to_uppercase()
))
.플러그인등록(클로저플러그인::new(
"길이계산".to_string(),
|텍스트| format!("길이: {}", 텍스트.len())
))
.플러그인등록(클로저플러그인::new(
"역순변환".to_string(),
|텍스트| 텍스트.chars().rev().collect()
))
let 결과들 = 매니저.모든플러그인실행("hello world")
assert 결과들.len() == 3
match 매니저.플러그인찾기("대문자변환") { case Some(대문자플러그인) => assert 대문자플러그인.실행("test") == "TEST", case None => {} }
// 미들웨어 테스트
let 체인 = 미들웨어체인::new()
.미들웨어추가(|값: i32, 다음| {
print!("미들웨어 1: 입력 {}", 값)
let 결과 = 다음(값 + 1)
print!("미들웨어 1: 출력 {}", 결과)
결과
})
.미들웨어추가(|값, 다음| {
print!("미들웨어 2: 입력 {}", 값)
let 결과 = 다음(값 * 2)
print!("미들웨어 2: 출력 {}", 결과)
결과
})
let 최종결과 = 체인.실행(5, |값| {
print!("최종 핸들러: {}", 값)
값 + 10
})
assert 최종결과 == 22 // ((5 + 1) * 2) + 10
}
// 사용 예시
let 텍스트처리매니저 = 플러그인매니저::new()
.플러그인등록(클로저플러그인::new(
"HTML이스케이프".to_string(),
|텍스트| 텍스트.replace("&", "&").replace("<", "<").replace(">", ">")
))
.플러그인등록(클로저플러그인::new(
"마크다운강조".to_string(),
|텍스트| format!("**{}**", 텍스트)
))
let 처리결과들 = 텍스트처리매니저.모든플러그인실행("Hello <world> & friends!")
for (플러그인명, 결과) in 처리결과들 {
print!("{}: {}", 플러그인명, 결과)
}
🔍 클로저 성능 최적화
메모리 효율적인 클로저
// 참조 기반 클로저 (메모리 효율적)
function 참조기반클로저(데이터: &[i32]) -> impl Fn(usize) -> Option<i32> + '_ {
move |인덱스| 데이터.get(인덱스).copied()
}
// 스마트 포인터 활용
use std::rc::Rc
use std::sync::Arc
function 공유클로저생성(공유데이터: Rc<Vec<i32>>) -> impl Fn(usize) -> Option<i32> {
move |인덱스| 공유데이터.get(인덱스).copied()
}
function 스레드안전클로저생성(공유데이터: Arc<Vec<i32>>) -> impl Fn(usize) -> Option<i32> + Send + Sync {
move |인덱스| 공유데이터.get(인덱스).copied()
}
// 지연 초기화 클로저
struct 지연초기화<T, F>
where
F: FnOnce() -> T
{
초기화함수: Option<F>,
값: Option<T>
}
impl<T, F> 지연초기화<T, F>
where
F: FnOnce() -> T
{
function new(초기화함수: F) -> Self {
지연초기화 {
초기화함수: Some(초기화함수),
값: None
}
}
function get(&mut self) -> &T {
if self.값.is_none() {
let 초기화함수 = self.초기화함수.take().unwrap()
self.값 = Some(초기화함수())
}
self.값.as_ref().unwrap()
}
}
// 클로저 풀링
struct 클로저풀<F, T, U>
where
F: Fn(T) -> U
{
함수들: Vec<F>,
현재인덱스: usize
}
impl<F, T, U> 클로저풀<F, T, U>
where
F: Fn(T) -> U + Clone
{
function new(함수: F, 크기: usize) -> Self {
클로저풀 {
함수들: vec![함수; 크기],
현재인덱스: 0
}
}
function 실행(&mut self, 입력: T) -> U {
let 함수 = &self.함수들[self.현재인덱스]
self.현재인덱스 = (self.현재인덱스 + 1) % self.함수들.len()
함수(입력)
}
} test {
// 공유 클로저 테스트
let 데이터 = Rc::new(vec![1, 2, 3, 4, 5])
let 클로저1 = 공유클로저생성(데이터.clone())
let 클로저2 = 공유클로저생성(데이터.clone())
assert 클로저1(0) == Some(1)
assert 클로저2(4) == Some(5)
// 지연 초기화 테스트
let mut 지연값 = 지연초기화::new(|| {
print!("비용이 큰 계산 실행됨")
42
})
assert *지연값.get() == 42
assert *지연값.get() == 42 // 두 번째 호출에서는 계산 안 함
// 클로저 풀 테스트
let mut 풀 = 클로저풀::new(|x: i32| x * 2, 3)
assert 풀.실행(5) == 10
assert 풀.실행(3) == 6
}
// 성능 벤치마킹을 위한 클로저
function 벤치마크<F, T>(이름: &str, 함수: F, 입력: T) -> T
where
F: FnOnce(T) -> T
{
use std::time::Instant
let 시작시간 = Instant::now()
let 결과 = 함수(입력)
let 경과시간 = 시작시간.elapsed()
print!("{} 실행 시간: {:?}", 이름, 경과시간)
결과
}
// 캐시가 있는 클로저
use std::collections::HashMap
struct 캐시클로저<F, K, V>
where
F: Fn(K) -> V,
K: Clone + std::hash::Hash + Eq,
V: Clone
{
함수: F,
캐시: HashMap<K, V>
}
impl<F, K, V> 캐시클로저<F, K, V>
where
F: Fn(K) -> V,
K: Clone + std::hash::Hash + Eq,
V: Clone
{
function new(함수: F) -> Self {
캐시클로저 {
함수,
캐시: HashMap::new()
}
}
function 실행(&mut self, 키: K) -> V {
match self.캐시.get(&키) { case Some(값) => return 값.clone(), case None => {} }
let 결과 = (self.함수)(키.clone())
self.캐시.insert(키, 결과.clone())
결과
}
function 캐시크기(&self) -> usize {
self.캐시.len()
}
function 캐시지우기(&mut self) {
self.캐시.clear()
}
}
// 사용 예시
let 공유벡터 = Arc::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
let 접근클로저 = 스레드안전클로저생성(공유벡터)
print!("값 접근: {:?}", 접근클로저(3))
let mut 피보나치캐시 = 캐시클로저::new(|n: i32| -> i64 {
if n <= 1 { n as i64 } else {
// 실제로는 재귀적으로 캐시를 사용해야 하지만,
// 여기서는 단순화된 버전
(n as i64).pow(2) // 예시를 위한 간단한 계산
}
})
벤치마크("피보나치 계산", |_| {
for i in 1..=20 {
피보나치캐시.실행(i)
}
}, ())
print!("캐시 크기: {}", 피보나치캐시.캐시크기())
🎯 클로저 마스터하기
클로저는 토파즈의 가장 강력한 기능 중 하나입니다:
✅ 클로저 사용 시기:
- 콜백 함수와 이벤트 처리
- 고차 함수 구현
- 상태 캡슐화가 필요한 경우
- 지연 실행과 부분 적용
⚠️ 주의사항:
- 메모리 누수 방지 (순환 참조 주의)
- 성능 고려 (불필요한 클로저 생성 피하기)
- 라이프타임 관리 (적절한 캡처 방식 선택)
- 디버깅 복잡성 인식
🚀 토파즈 클로저의 장점:
- 타입 안전성 보장
- 자동 메모리 관리
- 다양한 캡처 방식 지원
- 함수형 프로그래밍 패러다임 완벽 지원
클로저와 함께 더욱 표현력 있는 코드를 작성하세요! ✨🚀