제네릭과 트레이트
impl Trait
impl Trait
can be used in two locations:
- as an argument type
- 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); }