고급 함수와 클로저

Beginner

This tutorial is from open-source community. Access the source code

소개

고급 함수와 클로저에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.

이 랩에서는 함수 포인터와 클로저 반환을 포함하여 함수와 클로저의 고급 기능을 살펴봅니다.

고급 함수와 클로저

이 섹션에서는 함수 포인터와 클로저 반환을 포함하여 함수와 클로저와 관련된 몇 가지 고급 기능을 살펴봅니다.

함수 포인터

클로저를 함수에 전달하는 방법에 대해 이야기했습니다. 일반 함수도 함수에 전달할 수 있습니다! 이 기술은 새로운 클로저를 정의하는 대신 이미 정의한 함수를 전달하려는 경우에 유용합니다. 함수는 Fn 클로저 트레이트와 혼동하지 않도록 소문자 f 를 사용하여 fn 타입으로 강제 변환됩니다. fn 타입은 함수 포인터 라고 합니다. 함수 포인터를 사용하여 함수를 전달하면 다른 함수의 인수로 함수를 사용할 수 있습니다.

매개변수가 함수 포인터임을 지정하는 구문은 클로저의 구문과 유사합니다. Listing 19-27 에 표시된 것처럼, 매개변수에 1 을 더하는 함수 add_one을 정의했습니다. 함수 do_twice는 두 개의 매개변수를 받습니다. i32 매개변수를 받고 i32를 반환하는 함수에 대한 함수 포인터와 하나의 i32 값입니다. do_twice 함수는 함수 f를 두 번 호출하여 arg 값을 전달한 다음 두 함수 호출 결과를 더합니다. main 함수는 add_one5 인수를 사용하여 do_twice를 호출합니다.

Filename: src/main.rs

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {answer}");
}

Listing 19-27: fn 타입을 사용하여 함수 포인터를 인수로 받기

이 코드는 The answer is: 12를 출력합니다. do_twice의 매개변수 fi32 타입의 매개변수 하나를 받고 i32를 반환하는 fn임을 지정합니다. 그런 다음 do_twice의 본문에서 f를 호출할 수 있습니다. main에서 함수 이름 add_onedo_twice의 첫 번째 인수로 전달할 수 있습니다.

클로저와 달리 fn은 트레이트가 아닌 타입이므로, Fn 트레이트 중 하나를 사용하여 제네릭 타입 매개변수를 트레이트 바운드로 선언하는 대신 fn을 매개변수 타입으로 직접 지정합니다.

함수 포인터는 세 가지 클로저 트레이트 (Fn, FnMut, FnOnce) 를 모두 구현하므로, 함수 포인터를 클로저를 예상하는 함수의 인수로 항상 전달할 수 있습니다. 함수가 함수 또는 클로저를 모두 수용할 수 있도록 제네릭 타입과 클로저 트레이트 중 하나를 사용하여 함수를 작성하는 것이 가장 좋습니다.

그렇긴 하지만, fn만 허용하고 클로저는 허용하지 않으려는 한 가지 예는 클로저가 없는 외부 코드와 인터페이스할 때입니다. C 함수는 함수를 인수로 받을 수 있지만 C 에는 클로저가 없습니다.

인라인으로 정의된 클로저 또는 명명된 함수를 모두 사용할 수 있는 예로, 표준 라이브러리의 Iterator 트레이트에서 제공하는 map 메서드를 살펴보겠습니다. map 함수를 사용하여 숫자 벡터를 문자열 벡터로 바꾸려면 다음과 같이 클로저를 사용할 수 있습니다.

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(|i| i.to_string())
    .collect();

또는 다음과 같이 클로저 대신 map의 인수로 함수 이름을 지정할 수 있습니다.

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(ToString::to_string)
    .collect();

"고급 트레이트"에서 이야기했듯이 to_string이라는 이름의 여러 함수가 있으므로 정규화된 구문을 사용해야 합니다.

여기서는 ToString 트레이트에 정의된 to_string 함수를 사용하고 있으며, 표준 라이브러리는 Display를 구현하는 모든 타입에 대해 이를 구현했습니다.

"열거형 값"에서 정의한 각 열거형 변수의 이름도 초기화 함수가 된다는 것을 기억하십시오. 이러한 초기화 함수를 클로저 트레이트를 구현하는 함수 포인터로 사용할 수 있습니다. 즉, 초기화 함수를 다음과 같이 클로저를 사용하는 메서드의 인수로 지정할 수 있습니다.

enum Status {
    Value(u32),
    Stop,
}

let list_of_statuses: Vec<Status> = (0u32..20)
    .map(Status::Value)
    .collect();

여기서는 Status::Value의 초기화 함수를 사용하여 map이 호출되는 범위의 각 u32 값으로 Status::Value 인스턴스를 생성합니다. 어떤 사람들은 이 스타일을 선호하고, 어떤 사람들은 클로저를 사용하는 것을 선호합니다. 동일한 코드로 컴파일되므로 자신에게 더 명확한 스타일을 사용하십시오.

클로저 반환

클로저는 트레이트로 표현되므로 클로저를 직접 반환할 수 없습니다. 트레이트를 반환하려는 대부분의 경우, 대신 트레이트를 구현하는 구체적인 타입을 함수의 반환 값으로 사용할 수 있습니다. 그러나 클로저는 반환 가능한 구체적인 타입을 갖지 않으므로 그렇게 할 수 없습니다. 예를 들어 함수 포인터 fn을 반환 타입으로 사용할 수 없습니다.

다음 코드는 클로저를 직접 반환하려고 시도하지만 컴파일되지 않습니다.

fn returns_closure() -> dyn Fn(i32) -> i32 {
    |x| x + 1
}

컴파일러 오류는 다음과 같습니다.

error[E0746]: return type cannot have an unboxed trait object
 --> src/lib.rs:1:25
  |
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at
compile-time
  |
  = note: for information on `impl Trait`, see
<https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-
implement-traits>
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of
type `[closure@src/lib.rs:2:5: 2:14]`, which implements `Fn(i32) -> i32`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~~~~~~~~~~~~~~~~

오류는 다시 Sized 트레이트를 참조합니다! Rust 는 클로저를 저장하는 데 얼마나 많은 공간이 필요한지 알지 못합니다. 이 문제에 대한 해결책을 앞서 보았습니다. 트레이트 객체 (trait object) 를 사용할 수 있습니다.

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

이 코드는 문제없이 컴파일됩니다. 트레이트 객체에 대한 자세한 내용은 "다양한 타입의 값을 허용하는 트레이트 객체 사용"을 참조하십시오.

다음으로, 매크로를 살펴보겠습니다!

요약

축하합니다! 고급 함수 및 클로저 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.