제네릭 (Generics)
이 포스트는 Rust를 처음 공부하면서 정리한 내용입니다. 해당 포스트는 개인적으로 기억하기 위한 메모 성격에 가깝습니다. “러스트 프로그래밍 공식 가이드 (2018, 제이펍)” 서적을 참고하였으며, 러스트 공식 사이트 에서 책과 동일한 내용을 찾을 수 있습니다. 따라서 더 자세한 내용을 찾으시면 위의 링크를 참고하시면 좋습니다.
제네릭 프로그래밍 (generic programming)이란 아직 알지 못하는 데이터 타입을 위해 효율적이고 재사용 가능한 코드를 작성하기 위한 기법입니다. C++에서 익숙한 개념을 불러오자면 템플릿(template)이 되겠네요. C++에서 템플릿을 작성하는 방법은 아래와 같았습니다.
template <class T> class X;
template <class T>
void some_function(T argT) { };
template
키워드와 class
또는 typename
키워드를 조합하여 선언부에 같이 작성했었습니다. 이와 같이 Rust에서의 제네릭 타입이란 C++에서의 템플릿과 비슷한 개념입니다. 이전 포스트에서 보았던 Option<T>
, Result<T, E>
등이 제테릭 타입의 대표적인 예시가 됩니다.
이 포스트에서는 Rust 에서의 제네릭 타입에 대한 자세한 개념 보다는 제네릭 타입 (또는 제네릭 데이터 타입)에 대한 사용법 위주로 작성하겠습니다. 참고자료
함수에서의 정의: Generic Functions
일반적으로 제네릭 함수를 정의하는 경우에는 특정 타입의 매개변수나 리턴 타입을 사용하는 함수의 시그니쳐에서 사용합니다.
fn takes_anything<T>(x: T) {
// Do something with `x`.
}
fn takes_two_of_the_same_things<T>(x: T, y: T) {
// ...
}
fn takes_two_things<T, U>(x: T, y: U) {
// ...
}
참고: Rust에서의 타입 이름 규칙은 CamelCase 이므로 하나의 대문자를 사용합니다.
구조체에서의 정의: Generic Structs
아래와 같이 사용할 수도 있습니다.
struct Point<T> {
x: T, // T type
y: T, // T type
}
let int_origin = Point { x: 0, y: 0 };
let float_origin = Point { x: 0.0, y: 0.0 };
열거자에서의 정의: Generic Enums
이전에 사용했던 Option<T>
와 Result<T, E>
에 대한 정의와 사용법에 대한 예시를 다시 한 번 가져와보겠습니다.
// Option
enum Option<T> {
Some(T),
None,
}
let x: Option<i32> = Some(5);
let y: Option<f64> = Some(5.0f64);
// Result
enum Result<T, E> {
Ok(T),
Err(E),
}
메서드에서의 정의: Generic Methods
일반적인 함수에서 제네릭 타입을 적용했던 것과 같이 메서드에서도 제네릭을 적용할 수 있습니다.
제네릭 타입 메서드
**impl 키워드 뒤에
struct Point<T> {
x: T,
y: T
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point {
x: 5,
y: 10
};
}
특정 T가 구체화된 타입의 메서드
아래는 특정 Point<f32>
타입일 때만 구체화되는 impl
블록을 나타내고 있습니다.
// 특정 Point<f32> 타입일 때만 구체화되는 impl 블록
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
이러한 방식은 모든 타입을 사용할 수 있는 Point
구조체 정의와는 다른 제네릭 타입의 메서드
struct Point<T, U> {
x: T,
y: U
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y
}
}
}
// Main
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
// ...
}
위의 mixup
메서드는 자신을 호출하는 Point
구조체와는 다른 타입의 Point
구조체를 매개변수로 사용하고 있습니다.