트레이트 바운드는 제너릭을 선언할 때 타입 매개변수가 반드시 어떤 트레이트(또는 여러 트레이트)를 구현해야 함을 지정하는 역할을 한다. 즉, 러스트의 제너릭 함수나 제너릭 타입에서 실제 타입으로 치환될 때 해당 타입이 특정 기능을 반드시 제공하도록 강제한다. 만약 트레이트 바운드가 없다면 제너릭이 요구하는 메서드나 연산을 사용할 때 컴파일러가 그 타입이 그러한 메서드를 지원하는지 알 수 없다. 따라서 러스트는 안전성 보장을 위해, 제너릭에서 사용되는 연산에 대응하는 트레이트 바운드를 명시하도록 요구한다.
트레이트 바운드는 여러 위치에서 활용된다. 함수 정의의 매개변수, 구조체와 열거형의 타입 매개변수, 그리고 메서드 정의에서도 조건을 지정할 수 있다. 제너릭 타입이 특정 트레이트를 구현하고 있어야 그 트레이트의 메서드를 자유롭게 호출하거나 연산을 수행할 수 있다. 다음 코드는 간단한 예시이다.
이 함수는 제너릭 타입 T를 받아서 그 값을 출력한다. println! 매크로는 디스플레이 형태로 출력하기 위해 std::fmt::Display 트레이트를 요구한다. 따라서 T가 반드시 Display 트레이트를 구현하고 있어야 이 함수가 호출될 수 있다. 만약 Display를 구현하지 않는 타입을 넘기면 컴파일 에러가 발생한다.
트레이트 바운드는 : 오른쪽에 원하는 트레이트를 명시하는 형태로 작성한다. 필요에 따라 여러 트레이트를 동시에 요구할 수도 있다. 예를 들어 T: Clone + Default처럼 더하기 연산자로 연결하면 T가 Clone과 Default를 모두 구현해야 함을 의미한다. 여러 제약이 붙을 때 코드 가독성을 위해 where 절을 사용하기도 한다. 다음 예시는 여러 제약이 있을 때 where 절을 통해 더 명확하게 작성하는 방법이다.
이 함수는 T 타입이 Debug와 Clone을 모두 구현해야 하며, U 타입이 Display와 Default를 구현해야 한다. where 절을 사용하면 트레이트 바운드를 함수 선언부 뒤에 정리할 수 있어 선언부가 길어졌을 때 가독성이 좋아진다.
트레이트 바운드를 적용하는 가장 큰 이유는 컴파일 타임에 타입 안정성을 보장하기 위함이다. 예를 들어 두 값을 비교하려면 PartialEq 트레이트가 필요하고, 두 값을 정렬 기준으로 삼으려면 Ord 혹은 PartialOrd 트레이트가 필요하다. 러스트는 이러한 트레이트 구현 여부를 바운드로 명시적으로 작성하도록 권장함으로써 코드에서 발생할 수 있는 모호함이나 런타임 에러를 미연에 방지한다.
구조체나 열거형에서도 트레이트 바운드를 설정할 수 있다. 예를 들어 어떤 구조체의 타입 매개변수 T를 저장해야 하고 그 타입이 특정 트레이트의 기능을 사용해야 한다면 구조체 정의에서 트레이트 바운드를 추가한다. 다음 코드는 T가 Debug를 구현해야 하는 구조체 예시다.
위 구조체는 내부에 저장되는 value가 Debug 형태로 출력 가능해야 한다. 따라서 제너릭 매개변수 T에 Debug 트레이트를 바운드로 걸어 두었다. 구조체뿐 아니라 열거형 역시 같은 방식으로 트레이트 바운드를 설정할 수 있다.
또 다른 중요한 개념으로서는, 트레이트 바운드를 통해 제너릭을 제한하지 않으면 그 타입에 대해 특정 동작이 사용 불가능하다는 점이다. 예를 들어 어떤 함수에서 + 연산자를 사용하려면 해당 타입이 std::ops::Add 트레이트(또는 그 서브 트레이트)를 구현해야 한다. 이런 식으로 연산자를 다루려면 반드시 트레이트 바운드를 통해 그 연산자를 지원할 것임을 선언해야 컴파일이 통과된다. 이는 러스트가 모호한 타입 추론이나 암묵적 기능 사용을 허용하지 않는 엄격한 언어 철학을 지녔기 때문이다.
트레이트 바운드는 러스트에서 상당히 강력한 기능이며, 제너릭을 타입 추론과 함께 쓰는 핵심 요소이다. 컴파일러가 제너릭에 대해 가능한 최적화를 수행하기 위해서는 제약 범위를 명확히 알아야 한다. 트레이트 바운드가 없다면 해당 제너릭 타입으로 할 수 있는 일들이 매우 제한되거나, 아예 컴파일이 불가능해진다. 반대로 너무 많은 트레이트 바운드를 남발하면 코드 재사용성이 떨어지므로, 필요한 수준에서만 적절히 트레이트 바운드를 설정해야 한다.
트레이트 바운드를 종합적으로 이해하려면, 어떤 함수나 구조체의 내부에서 사용되는 연산이나 메서드가 정확히 어느 트레이트에 속해 있는지 파악해야 한다. 예를 들어 디버그 출력이 필요하면 Debug 트레이트 바운드, 디스플레이 출력이 필요하면 Display 트레이트 바운드가 필수적이다. 비공개 메서드나 내부 구현 세부사항에서라도 해당 트레이트를 사용하는 순간, 반드시 바운드를 표시해야만 컴파일 오류를 피할 수 있다. 이러한 방식으로 제너릭 타입이 안전하게 제한되고, 코드 독자가 타입 매개변수가 어떤 역할을 하는지 명확히 이해할 수 있다.
정리하면 트레이트 바운드는 제너릭 프로그래밍에서 타입 안정성과 명시성을 부여하는 필수적인 도구다. 제너릭 함수나 구조체가 사용하려는 동작(메서드나 연산)이 소속된 트레이트가 무엇인지 파악한 뒤, 그 트레이트를 바운드로 지정함으로써 컴파일러와 독자 모두에게 그 의도를 분명히 전달할 수 있다. 이렇게 명시적으로 작성된 제너릭 코드야말로 러스트에서 의도한 안전하고 강력한 추상화를 체감할 수 있는 대표적인 방식이다.