제네릭 (Generic)
제네릭 타입
함수, 메서드, struct, enum 등을 정의할 때 일반적으로 구체적인 데이타 타입을 지정하게 되는데, 이렇게 하는 대신 임의의 타입을 받아들일 수 있게 만든 것을 제네릭 (Generic)이라 한다. 제네릭은 함수, 메서드, struct, enum 등을 정의할 때 <T> 과 같은 타입 파라미터를 지정하여 정의하는데, 이때의 T에는 여러 가지 타입들(예를 들어, i32, f64, String 등)이 들어갈 수 있다. 제네릭의 타입 파라미터는 필요에 따라 하나 이상 여러 개를 지정할 수도 있다. 제네릭을 사용하면 모든 타입마다 중복된 코드를 작성하지 않아도 되고, 코드를 더 유연하게 사용할 수 있게 된다.
제네릭으로 간단한 struct를 만들어 보자. 아래는 Point 라는 구조체로서 2개의 점을 가진 제네릭 타입이다. 아래 예제에서 pt1 은 정수(i32)를 받아들이고 있으므로 컴파일러는 구조체를 Point<i32> 으로 사용하고, pt2 는 부동소수점 숫자를 받아 들이므로 Point<f64> 를 사용할 것이다.
struct Point<T> { x: T, y: T } fn main() { let pt1 = Point { x: 1, y: 1 }; let pt2 = Point { x: 2.0, y: 2.0 }; }
제네릭의 다른 예로, 아래 Result 열거형 타입은 표준 라이브러리에 들어 있는 타입으로 이 타입은 2개의 타입 파라미터(T, E)를 사용하는 열거형 제네릭 타입이다. Result 열거형에서 Ok(T) variant 에서는 T 라는 타입 파라미터를 사용하고, Err(E) 에서는 E 라는 타입 파라미터를 사용하고 있다.
enum Result<T, E> { Ok(T), Err(E), }
아래는 위의 Result 제네릭을 사용하는 예로서, 함수는 Result<String, io::Error> 타입을 리턴하고 있다. 즉 Ok variant의 경우는 String 타입의 데이타가 사용되고, Err variant의 경우는 std::io::Error 타입이 사용된다.
fn func1() -> Result<String, io::Error> { //... 생략... }
구조체 제네릭에 대한 메서드를 구현하기 위해서는, 아래 예제와 같이 impl<T> 와 같이 impl 뒤에 제네릭 타입 파라미터 T를 지정하고, 구조체(여기서는 Point<T>)에 대한 메서드를 추가하면 된다. 메서드는 함수와 마찬가지로, 타입 파라미터 T를 입력 파라미터 혹은 리턴 타입에 사용할 수 있다.
struct Point<T> { x: T, y: T } impl<T> Point<T> { fn get_x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 2.0, y: 2.0 }; println!("{}", p.get_x()); }
Rust에서 제네릭은 컴파일 타임에 구체적인 타입(concrete type)으로 대체되기 때문에, 런타임 성능에 어떤 영향도 미치지 않는다. 즉, 컴파일 타임에 이미 제네릭 타입을 구체적인 타입으로 대체하여 코드를 수행한다.