Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

impl Trait

impl Trait는 두 위치에서 사용될 수 있습니다:

  1. 인자 타입으로서
  2. 반환 타입으로서

인자 타입으로서

함수가 트레이트에 대해 제네릭이지만 구체적인 타입에는 관심이 없다면, 인자 타입으로 impl Trait를 사용하여 함수 선언을 단순화할 수 있습니다.

예를 들어, 다음 코드를 살펴보세요:

fn parse_csv_document<R: std::io::BufRead>(src: R) -> std::io::Result<Vec<Vec<String>>> {
    src.lines()
        .map(|line| {
            // 소스의 각 줄에 대해
            line.map(|line| {
                // 줄을 성공적으로 읽었다면 처리하고, 그렇지 않으면 에러를 반환합니다
                line.split(',') // 콤마로 구분된 줄을 분할합니다
                    .map(|entry| String::from(entry.trim())) // 앞뒤 공백을 제거합니다
                    .collect() // 한 행의 모든 문자열을 `Vec<String>`으로 수집합니다
            })
        })
        .collect() // 모든 줄을 `Vec<Vec<String>>`으로 수집합니다
}

parse_csv_document는 제네릭이어서 BufReader<File>이나 [u8]과 같이 BufRead를 구현하는 모든 타입을 취할 수 있지만, R이 어떤 타입인지는 중요하지 않고 R은 단지 src의 타입을 선언하는 데만 사용되므로, 다음과 같이 작성할 수도 있습니다:

fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> {
    src.lines()
        .map(|line| {
            // 소스의 각 줄에 대해
            line.map(|line| {
                // 줄을 성공적으로 읽었다면 처리하고, 그렇지 않으면 에러를 반환합니다
                line.split(',') // 콤마로 구분된 줄을 분할합니다
                    .map(|entry| String::from(entry.trim())) // 앞뒤 공백을 제거합니다
                    .collect() // 한 행의 모든 문자열을 `Vec<String>`으로 수집합니다
            })
        })
        .collect() // 모든 줄을 `Vec<Vec<String>>`으로 수집합니다
}

impl Trait를 인자 타입으로 사용하는 것은 여러분이 함수의 어떤 형태를 사용하는지 명시적으로 명시할 수 없음을 의미합니다. 즉, parse_csv_document::<std::io::Empty>(std::io::empty())는 두 번째 예제에서 작동하지 않습니다.

반환 타입으로서

여러분의 함수가 MyTrait를 구현하는 타입을 반환한다면, 반환 타입을 -> impl MyTrait로 작성할 수 있습니다. 이는 타입 시그니처를 아주 많이 단순화하는 데 도움이 될 수 있습니다!

use std::iter;
use std::vec::IntoIter;

// 이 함수는 두 개의 `Vec<i32>`를 결합하고 그에 대한 이터레이터를 반환합니다.
// 반환 타입이 얼마나 복잡한지 보세요!
fn combine_vecs_explicit_return_type(
    v: Vec<i32>,
    u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
    v.into_iter().chain(u.into_iter()).cycle()
}

// 이것은 정확히 동일한 함수이지만, 반환 타입에 `impl Trait`를 사용합니다.
// 훨씬 더 간단해진 것을 보세요!
fn combine_vecs(
    v: Vec<i32>,
    u: Vec<i32>,
) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u.into_iter()).cycle()
}

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5];
    let mut v3 = combine_vecs(v1, v2);
    assert_eq!(Some(1), v3.next());
    assert_eq!(Some(2), v3.next());
    assert_eq!(Some(3), v3.next());
    assert_eq!(Some(4), v3.next());
    assert_eq!(Some(5), v3.next());
    println!("모두 완료");
}

더 중요한 점은, 일부 Rust 타입은 명시적으로 작성할 수 없다는 것입니다. 예를 들어, 모든 클로저는 자신만의 이름 없는 구체적인 타입을 가집니다. impl Trait 구문이 생기기 전에는 클로저를 반환하기 위해 힙에 할당해야만 했습니다. 하지만 이제는 다음과 같이 정적으로 처리할 수 있습니다:

// 입력에 `y`를 더하는 함수를 반환합니다.
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    let closure = move |x: i32| { x + y };
    closure
}

fn main() {
    let plus_one = make_adder_function(1);
    assert_eq!(plus_one(2), 3);
}

또한 impl Trait를 사용하여 map이나 filter 클로저를 사용하는 이터레이터를 반환할 수도 있습니다! 이는 mapfilter를 더 쉽게 사용할 수 있게 해줍니다. 클로저 타입은 이름이 없기 때문에, 함수가 클로저를 포함하는 이터레이터를 반환할 경우 명시적인 반환 타입을 작성할 수 없습니다. 하지만 impl Trait를 사용하면 이를 쉽게 할 수 있습니다:

fn double_positives<'a>(numbers: &'a Vec<i32>) -> impl Iterator<Item = i32> + 'a {
    numbers
        .iter()
        .filter(|x| x > &&0)
        .map(|x| x * 2)
}

fn main() {
    let singles = vec![-3, -2, 2, 3];
    let doubles = double_positives(&singles);
    assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]);
}