익명 함수

익명 함수란 이름이 없는 함수라는 뜻으로, 프로그램 내에서 변수에 할당하거나 다른 함수에 파라미터로 전달되는 함수입니다. 따라서 익명 함수를 먼저 만들어 놓고 나중에 함수를 실행할 수 있습니다.

파이썬에서는 익명 함수를 람다 함수(Lambda function)이라고 부릅니다. lambda 키워드를 쓰고, 파라미터: 리턴값 형식으로 함수의 내용을 정의합니다. 이렇게 만든 람다 함수를 변수 my_func 에 할당해 두었다가 print 함수 안에서 호출하는 예제입니다.

my_func = lambda x: x + 1
print(my_func(3))

실행 결과

4

위 예제처럼 람다 함수는 다른 함수에 파라미터로 전달하는 것이 가능합니다. 러스트에도 람다 함수와 비슷한 개념이 있는데 바로 클로저(Closure)입니다. 위에서 만든 람다 함수와 동일한 기능을 하는 클로저를 만들어 보겠습니다. 클로저는 파라미터를 | | 의 사이에 선언하고, 그 뒤에 함수에서 리턴하는 부분을 작성합니다.

fn main() {
    let my_func = |x| x + 1;
    println!("{}", my_func(3));
}

실행 결과

4

이때 컴파일러가 클로저의 파라미터와 리턴값의 타입을 i32로 추측해서 보여줍니다. 이는 실제 함수가 실행되는 부분인 my_func(3)로부터 변수 x의 타입을 알 수 있기 때문입니다. 이처럼 클로저는 함수와 다르게 타입을 명시할 필요가 없이 컴파일러가 타입을 추론하도록 할 수 있습니다. 하지만 타입을 명시하는 것도 가능합니다.

fn main() {
    let my_func = |x: i32| -> i32 { x + 1 };
    println!("{}", my_func(3));
}

타입을 명시해야 하는 경우, 함수 실행 부분을 중괄호로 묶어 주어야 합니다.

람다 함수는 반드시 한 줄로만 작성해야 하지만, 클로저는 중괄호로 묶어주는 경우 여러 줄을 작성할 수 있습니다. 위 코드를 하나의 클로저로 바꿔 보겠습니다. 이때 입력받은 변수 x의 값을 바꾸기 위해 가변으로 선언하고, 첫 번째 줄에서 x에 1을 더해줍니다. 그 다음 x를 프린트합니다. 이제 my_func를 호출하면 동일하게 4가 출력됩니다.

fn main() {
    let my_func = |mut x: i32| {
        x = x + 1;
        println!("{}", x);
    };

    my_func(3);
}

참고로 파이썬과는 다르게 클로저의 재귀 호출은 아직 지원되지 않습니다. 파이썬에서 클로저를 이용해 피보나치 수를 계산하는 예제는 다음과 같습니다.

def fibonacci(n):
    cache = {}

    def fib(n):
        if n in cache:
            return cache[n]
        if n < 2:
            return n
        cache[n] = fib(n - 1) + fib(n - 2)
        return cache[n]

    return fib(n)


fibonacci(10)

동일한 로직을 구현한 러스트 코드는 클로저가 자기 자신을 부를 수 없기 때문에 컴파일되지 않습니다.

fn fib(n: u32) -> u32 {
    let cache = vec![0, 1];
    let _fib = |n| {
        if n < cache.len() {
            cache[n]
        } else {
            let result = _fib(n - 1) + _fib(n - 2);
            cache.push(result);
            result
        }
    };
    _fib(n)
}

fn main() {
    println!("{}", fib(10));
}