제네릭과 트레이트

impl Trait

impl Trait can be used in two locations:

  1. as an argument type
  2. as a return type

파라미터 타입

If your function is generic over a trait but you don't mind the specific type, you can simplify the function declaration using impl Trait as the type of the argument.

fn copy(_item: impl Copy) {
    println!("Copy");
}

fn clone(_item: impl Clone) {
    println!("Clone");
}

fn main() {
    let num = 1;
    copy(num);
    clone(num);

    let string = String::from("Hello");
    clone(string);
    // copy(string); // 🤯
}

트레이트 바운드

트레이트 바운드(Trait bound)란 impl Trait 를 사용하는 대신 좀더 간결하게 표현할 수 있는 방법입니다.

use std::fmt::Display;

fn some_function<T: Display>(t: &T) {
    println!("{}", t);
}

fn main() {
    let x = 5;
    some_function(&x);
}

이를 원래대로 impl Trait를 사용하면 다음과 같습니다.

#![allow(unused)]
fn main() {
fn some_function(t: &impl Display) {
    println!("{}", t);
}
}

트레이트 바운드를 사용하면 다음과 같이 타입을 복합적으로 표현할 수 있습니다.

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) {}

하지만 이러면 함수 선언을 알아보기가 어려워지기 때문에 where 문을 사용해 좀더 읽기 쉽게 바꿀 수 있습니다.

use std::fmt::{Debug, Display};

fn some_function<T, U>(t: &T, u: &U)
where
    T: Display + Clone,
    U: Clone + Debug,
{
    println!("{} {:?}", t, u);
}

fn main() {
    let x = 5;
    let y = vec![1, 2, 3];
    some_function(&x, &y);
}

리턴 타입

리턴 타입으로 impl Trait 구문을 사용하면 특정 트레이트를 구현하고 있는 타입을 리턴하도록 헐 수도 있습니다:

fn double(vector: Vec<i32>) -> impl Iterator<Item = i32> {
    vector.into_iter().map(|x| x * 2)
}

fn main() {
    for num in double(vec![1, 2, 3]) {
        println!("{}", num);
    }
}

터보피시

터보 피쉬 신택스는 제네릭 타입인 파라미터에 구체적인 타입을 지정하는 데 사용됩니다.

identifier::<type>

타입 어노테이션 대신에 사용되는 경우

간결성을 위해 명시적 타입 어노테이션 대신에 사용됩니다.

컴파일러가 대부분의 상황에서 타입을 추론 가능

use std::collections::HashMap;

fn main() {
    let mut students = HashMap::new();
    students.insert("buzzi", 100);
}

이런 경우는 어떤 원소를 넣는지 알 수 없기 때문에 타입을 명시적으로 알려줘야 함

use std::collections::HashMap;

fn main() {
    let mut students: HashMap<&str, i32> = HashMap::new();
    // students.insert("buzzi", 100);
}

이 경우 터보피시를 사용해서 타입 어노테이션을 대체 가능

use std::collections::HashMap;

fn main() {
    let mut students: HashMap = HashMap::<&str, i32>::new();
    // students.insert("buzzi", 100);
}

복잡한 예제

fn double<T>(vector: Vec<T>) -> impl Iterator<Item = T> {
    vector.into_iter().map(|x| x)
}

fn main() {
    let nums = double(vec![1, 2, 3]).collect::<Vec<i32>>();
    println!("{:?}", nums);
    let nums: Vec<String> =
        double(vec!["1".to_string(), "2".to_string(), "3".to_string()]).collect();
    println!("{:?}", nums);
}

명시적 타입 어노테이션이 작동하지 않을 때

fn main() {
    let nums: Vec<i32> = ["1", "2", "three"]
        .iter()
        .filter_map(|x| x.parse().ok())
        .collect();
}
fn main() {
    let nums: bool = ["1", "2", "three"]
        .iter()
        .filter_map(|x| x.parse().ok())
        .collect() // 🤯
        .contains(&1);
}
fn main() {
    let nums: bool = ["1", "2", "three"]
        .iter()
        .filter_map(|x| x.parse().ok())
        .collect::<Vec<i32>>()
        .contains(&1);
}