# 제너릭의 기초

러스트에서 제너릭(Generic)은 여러 종류의 타입에 대해 동작할 수 있는 추상적인 코드 구성을 가능하게 해주는 강력한 기능이다. 이를 통해 개발자는 코드 재사용성을 높이고, 다양한 타입을 안전하게 다룰 수 있다. 제너릭은 주로 함수, 구조체, 열거형, 메서드 정의 등에서 사용되며, 실제 코드가 컴파일될 때 구체적인 타입으로 치환(monomorphization)되어 성능상으로도 큰 이점이 있다.

제너릭을 사용하면 우선 함수나 자료구조 정의 시점에서 타입을 고정하지 않는다. 예를 들어, i32 타입이든 f64 타입이든 혹은 사용자가 정의한 구조체 타입이든, 동일한 로직으로 처리할 수 있는 공통된 코드 패턴을 작성할 수 있다. 그리고 최종적으로 실제 인스턴스를 생성할 때나 함수를 호출할 때 구체적인 타입이 결정되어 빌드 과정에서 최적화된 기계어 코드가 생성된다. 이러한 특성 덕분에 제너릭 코드가 런타임 비용을 늘리지 않으면서도 강력한 추상화와 높은 성능을 동시에 제공할 수 있다.

제너릭 파라미터는 일반적으로 대괄호 <> 안에 표시하며, 관례적으로 대문자 T, U, V 등을 사용하지만 필수 규칙은 아니다. 함수에서 사용하는 가장 간단한 형태의 제너릭 예시는 다음과 같다.

```
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut max = list[0];
    for &item in list.iter() {
        if item > max {
            max = item;
        }
    }
    max
}
```

이 함수는 PartialOrd 트레이트를 구현한 타입(비교 연산이 가능한 타입)과 Copy 트레이트를 구현한 타입(간단한 복사 세미antics을 지닌 타입)에 대해 작동한다. 이렇게 제너릭 파라미터 T에 특정 트레이트 구현을 요구하는 것을 트레이트 바운드(trait bound)라고 부른다. 이 예시의 largest 함수는 i32, f64, char 같은 기본 타입뿐 아니라, PartialOrd + Copy를 구현한 임의의 사용자 정의 타입에도 적용 가능하다.

제너릭은 함수뿐 아니라 구조체나 열거형 정의에도 사용할 수 있다. 구조체 정의에서 제너릭을 사용하는 방식은 다음과 같다.

```
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
}
```

이 구조체 Point는 x와 y 필드에 동일한 타입 T를 사용한다. 만약 x와 y가 서로 다른 타입을 가질 수도 있도록 정의하려면 별도의 타입 파라미터를 명시할 수 있다.

```
struct Point2D<X, Y> {
    x: X,
    y: Y,
}
```

열거형에서도 제너릭은 동일한 방식으로 동작한다. 예를 들어 Result\<T, E> 열거형은 T 타입의 정상 결과와 E 타입의 에러 값을 표현한다. 표준 라이브러리의 Result나 Option도 모두 제너릭 기반으로 정의되어 있다. 사용자는 자신이 원하는 타입을 대입해서 원하는 형태로 사용할 수 있다.

컴파일 시점에 실제 타입이 치환되는 과정인 모노모픽화(monomorphization)는 런타임 비용을 유발하지 않고, 각 타입별로 최적화된 코드가 생성되므로 성능에 유리하다. 다만 바이너리의 크기가 약간 증가할 수 있다는 점은 고려할 만하다. 그러나 러스트는 불필요한 코드 중복을 줄이는 다양한 최적화 기법을 사용하기 때문에, 실제로는 지나치게 큰 바이너리가 생성되는 일은 흔치 않다.

제너릭을 다룰 때 가장 주의해야 할 점은, 특정 작업을 수행하기 위해서는 그 작업을 수행할 수 있는 트레이트가 요구될 수 있다는 것이다. 예를 들어 + 연산을 수행하려면 Add 트레이트가, 비교 연산을 하려면 PartialOrd나 Ord 트레이트가 필요하다. 이처럼 제너릭 파라미터에 요구 조건을 설정하는 것이 트레이트 바운드이며, 함수 시그니처나 impl 구문에서 사용된다. 이러한 트레이트 바운드는 제너릭 타입이 무엇이든 간에 특정 동작이 가능함을 컴파일러가 보장할 수 있도록 해 준다.

제너릭 타입을 사용하는 메서드는 impl 정의에서도 자주 볼 수 있다. 구조체의 메서드 내에서 같은 제너릭 파라미터를 재사용할 수도 있고, 다른 파라미터를 추가로 받을 수도 있다. 제너릭에 대한 이해가 깊어지면, 여러 트레이트 바운드를 결합하여 복잡한 로직을 추상화하는 고급 기법도 활용할 수 있다.

제너릭은 안전성과 추상화, 그리고 성능을 모두 만족시키는 러스트의 강력한 기능으로서, 다양한 상황에서 반복되는 로직을 중복 작성하지 않고도 재사용 가능한 코드를 작성하도록 돕는다. 함수, 구조체, 열거형, 트레이트 정의, 그리고 모듈 전반에 걸쳐 제너릭 기법을 적극적으로 활용하면, 유지보수성과 성능을 고루 갖춘 우수한 러스트 코드를 작성할 수 있다.
