제네릭 (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 키워드 뒤에 를 추가**하면 Rust는 **Point 구조체 뒤의 표시가 구체화된 타입이 아니라 제네릭 타입이라는 점을 인식**합니다.

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 대신 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 구조체를 매개변수로 사용하고 있습니다.

태그:

카테고리:

업데이트: