1. Generic
제네릭은 여러 데이터 타입을 일반화해서 한 번에 구현할 수 있도록 하는 기능을 말합니다.
제네릭은 다른 언어에서도 많이 사용 되기 때문에 Rust에만 있는 특별한 기능은 아닙니다.
제네릭을 설명할 때 흔하게 볼 수 있는 예시를 Rust 코드로 만들어 보겠습니다.
fn add(x:i32, y:i32)->i32{
x + y
}
fn add(x:f64, y:f64)->f64{
x + y
}
fn main() {
let res1 = add(1,2);
let res2 = add(3.0,4.0);
println!("integer:{}, float:{}", res1, res2);
}
// 출력
...에러
..`add` must be defined only once in the value namespace of this module
두 파라미터를 더헤서 반환하는 함수 add를 i32, f64 두 가지 타입의 파라미터로 나누어서 구현했습니다.
어떤 언어에서는 함수의 이름이 같더라도 파라미터의 데이터 타입이나 파라미터 개수가 다르면 구현이 가능 합니다.
그런데 Rust에서는 이것을 차단하고 있습니다.
에러 메시지를 보면 같은 네임스페이스에 같은 이름의 함수를 또 만들 수 없다는 것을 알려 주고 있습니다.
그래서 이름을 달리 해서 만들어 봤습니다.
fn add1(x:i32, y:i32)->i32{
x + y
}
fn add2(x:f64, y:f64)->f64{
x + y
}
fn main() {
let res1 = add1(1,2);
let res2 = add2(3.0,4.0);
println!("integer:{}, float:{}", res1, res2);
}
// 출력
integer:3, float:7
이처럼 같은 기능을 하는 함수를 파라미터의 데이터 타입 때문에 다른 이름으로 계속 만드는 비효율적인 일을 하지 않기 위해 제네릭을 사용합니다.
이제 제네릭을 사용해서 간단히 해 보겠습니다.
fn add1<T: std::ops::Add<Output = T>>(x:T, y:T)->T{
x + y
}
fn main() {
let res1 = add1(1,2);
let res2 = add1(3.0,4.0);
println!("integer:{}, float:{}", res1, res2);
}
// 출력
integer:3, float:7
함수 add1을 봅시다.
fn add1<T: std::ops::Add<Output = T>>(x:T, y:T)->T
이 부분은 T를 제네릭 타입 이름으로 해서 구현한 것입니다.
함수 이름 add1 바로 뒤의 <T ……> 부분은 이 함수가 제네릭을 사용한다고 알려주는 역할을 합니다.
그리고 파라미터와 반환형에도 타입을 T라고 명시했습니다.
이것은 파라미터와 반환값의 타입이 같다는 의미입니다.
일반적으로 이 부분은
fn add1<T>(x:T, y:T)->T
이렇게 작성하면 됩니다.
그런데 위 코드에서 이렇게 구현해 보면 에러가 납니다.
에러 메시지를 보면, 함수 구현부의 “+” 연산자 때문임을 알 수 있습니다.
즉 T 타입이 “+” 연산자로 연산 가능한지 알 수가 없다는 것입니다.
하지만 친절한 Rust 컴파일러는 대안을 제시해 줍니다.
위의 구현은 그 대안대로 바꿔서 작성한 것입니다.. 그리고 잘 실행됩니다.
fn add1<T: std::ops::Add<Output = T>>
이 부분은 “+” 연산자가 T타입을 출력할 수 있게 허용합니다.
std::ops::Add은 뒤에서 트레이트를 소개할 때 더 자세히 설명할 예정입니다.
여기서는 std 크레이트에 기본으로 포함된 Add 트레이트라고만 알아 두십시오.
Add 트레이트는 “+” 연산을 가능하게 합니다.
다음은 앞에서 봤었던 Option enum의 정의입니다. 이것도 제네릭의 예입니다.
pub enum Option<T> {
None,
Some(T),
}
Option 뒤의 <T>는, Option enum이 제네릭 타입의 데이터를 사용할 것인데 그 타입 이름을 T라고 하겠다는 의미입니다. T 대신 V, Y, U 등 아무거나 사용해도 상관없지만 T를 관습적으로 많이 사용합니다.
Some(T)에서 T 타입의 데이터를 사용하고 있습니다.
<T>에서 사용하겠다고 정한 이름만을 사용해야 합니다.
T는 타입 유추에 의해서 구체적인 타입으로 결정되어 사용됩니다.
pub enum Result<T, E> {
Ok(T),
Err(E),
}
이 코드는 Result enum에 대한 정의입니다. 이 코드도 앞에서 봤던 것입니다.
여기서는 제네릭 타입이 한 번에 두 개 이상일 때의 사용법을 보여 주고 있습니다.
E 대신 다른 문자를 사용해도 되지만 에러임을 표현하기 위해 E를 사용한 것입니다.
상이한 제네릭 타입이 두 개 이상 사용될 때, 일반적으로 T, V, Y, U가 같이 잘 사용됩니다.
'Rust 입문' 카테고리의 다른 글
[Rust] 트레이트 바운드 (Trait Bound) (0) | 2022.10.13 |
---|---|
[Rust] Trait (0) | 2022.10.13 |
[Rust] use 사용하기 (0) | 2022.10.11 |
[Rust] super (1) | 2022.10.11 |
[Rust] 모듈 (파일) (0) | 2022.10.11 |