# 함수와 구조체에서의 제너릭

러스트에서 제너릭(Generic)은 다양한 타입을 지원하면서도 중복 코드를 줄이고, 컴파일 시점에 타입 안정성을 보장하기 위한 강력한 메커니즘이다. 제너릭을 사용하면 함수나 자료구조를 특정 타입에 구애받지 않고 작성할 수 있으며, 컴파일러는 이를 실제로 사용하는 시점에 구체적인 타입으로 치환해 최적화된 코드를 생성한다. 이를 단순화하면 “하나의 추상화된 코드”가 “필요할 때마다 서로 다른 타입에 대해 반복적으로 실체화(monomorphization)된다”고 볼 수 있다. 이 과정은 성능 오버헤드를 최소화하고 러스트가 지향하는 제로 코스트 추상화를 실현한다.

함수에 제너릭을 적용할 때는 타입 매개변수(T) 등을 함수 선언부에 명시한다. 제너릭 함수를 작성할 때 필요한 경우 트레이트 바운드(제너릭 타입에 적용해야 하는 트레이트 제약)도 함께 지정해야 한다. 예를 들어 어떤 타입이 특정 연산(비교, 덧셈 등)을 지원해야 한다면 그 연산과 대응되는 트레이트를 구현했는지 바운드로 강제한다. 이를 통해 컴파일러는 함수 내부에서 사용되는 제너릭 타입의 메서드나 연산이 합법적인지 검증할 수 있다.

아래 코드는 제너릭 함수를 간단히 보여 준다. 두 값을 비교하여 더 큰 값을 반환하는 함수이며, 비교 연산을 위해 제너릭 타입 T는 PartialOrd 트레이트를 구현해야 한다는 바운드가 걸려 있다.

```
fn larger<T: PartialOrd>(x: T, y: T) -> T {
    if x > y {
        x
    } else {
        y
    }
}
```

이 함수는 `larger(10, 20)`, `larger(3.14, 2.71)`과 같이 다양한 타입에 대해 문제없이 동작한다. 여기서 T는 호출 시점에 정해지는 타입으로 치환되어 함수 본문이 구체화된다고 볼 수 있다.

구조체에 제너릭을 적용할 때는 선언부에서 타입 파라미터를 명시하고, 필드 타입에서 이를 사용한다. 구조체 내부의 필드들이 서로 다른 제너릭 타입을 가질 수도 있다. 예를 들어 2차원 좌표를 나타내는 구조체를 생각해보자. 정수 좌표, 부동소수점 좌표, 그 외 사용자가 정의한 수치 타입을 모두 아우르기 위해서는 제너릭 구조체가 적합하다.

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

이 구조체는 Point, Point 등 다양한 타입으로 인스턴스화할 수 있다. 만약 x와 y의 타입이 서로 달라도 되도록 하고 싶다면 다음과 같이 타입 파라미터를 두 개로 분리할 수도 있다.

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

이렇게 작성하면 Point\<i32, f64>와 같이 x는 i32, y는 f64인 인스턴스를 생성할 수 있다. 제너릭 구조체를 정의할 때도 필요한 경우 트레이트 바운드를 통해 특정 연산을 제한할 수 있다. 예를 들어 구조체가 내부에서 어떤 메서드를 제공하려면 그 메서드에서 사용하는 연산이나 메서드를 제너릭 타입이 반드시 지원하도록 강제해야 한다.

```
struct Wrapper<T: std::fmt::Debug> {
    value: T,
}

impl<T: std::fmt::Debug> Wrapper<T> {
    fn print_debug(&self) {
        println!("{:?}", self.value);
    }
}
```

여기서 Wrapper는 T가 Debug 트레이트를 구현해야만 사용 가능하며, print\_debug 메서드 내에서 Debug 포매팅을 할 수 있게 된다.

함수나 구조체에서 제너릭을 사용할 때 중요한 개념은 단순 타입 추상화 이상의 강력한 제약 설정이 가능하다는 것이다. 트레이트 바운드를 적용함으로써 타입이 구현해야 할 행위를 엄밀하게 지정할 수 있고, 덕분에 제너릭 코드가 복잡해지더라도 안전성과 가독성을 유지할 수 있다. 그리고 러스트 컴파일러는 제너릭 코드가 호출될 때마다 실제 타입으로 치환해 모노몰픽 함수를 생성한다. 이 과정은 사용자 입장에선 추상화된 코드를 작성하지만, 실행 시점에는 해당 타입에 맞춰 최적화된 저수준 코드가 동작함을 의미한다.

제너릭 함수와 구조체를 다룰 때는 파라미터나 타입의 수가 늘어날수록 트레이트 바운드와 생명 주기(Lifetime) 등의 규칙을 함께 고려해야 한다. 예컨대 참조 타입을 제너릭으로 사용할 경우, 해당 참조가 유효한 범위를 컴파일러가 이해해야 하므로 생명 주기 표기와 같은 추가 문법이 동원될 수 있다. 이는 모든 상황에서 요구되지 않을 수 있지만, 타입이 복잡해질수록 러스트가 보장하는 안전성을 유지하기 위해 이러한 문법 역시 숙지해야 한다.

이처럼 함수와 구조체에서 제너릭을 적극적으로 활용하면 코드의 유연성과 재사용성이 크게 향상된다. 타입 안정성을 놓치지 않으면서 다양한 컨텍스트에서 같은 로직을 적용할 수 있고, 동시에 성능 손실을 최소화하기 때문에 러스트 프로그래밍에서 제너릭은 자주 사용되는 핵심 도구라고 할 수 있다.
