러스트에서 트레이트(Trait)는 타입이 구현해야 하는 기능을 정의하는 방법으로, 다른 언어의 인터페이스와 유사한 개념이다.
트레이트는 러스트의 타입 시스템에서 핵심적인 역할을 하며, 코드의 재사용성과 추상화를 가능하게 한다.
트레이트란?
트레이트는 타입이 가져야 할 공통된 동작을 정의하는 방법이다.
예를 들어, 여러 다른 타입들이 "출력 가능하다" 또는 "비교 가능하다"와 같은 공통된 기능을 가져야 할 때 트레이트를 사용한다.
러스트의 표준 라이브러리는 Display
, Debug
, Clone
, Copy
등 다양한 트레이트를 제공한다.
기본적인 사용법
트레이트는 크게 두 가지 측면에서 사용된다: 정의와 구현.
a. 트레이트 정의
pub trait Summary {
fn summarize(&self) -> String;
fn default_summary(&self) -> String {
String::from("(Read more...)")
}
}
summarize
: 구현체가 반드시 구현해야 하는 메서드default_summary
: 기본 구현이 제공되는 메서드로, 구현체가 재정의하지 않아도 됨
b. 트레이트 구현
struct NewsArticle {
headline: String,
content: String,
author: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {}", self.headline, self.author)
}
}
impl Summary for NewsArticle
: NewsArticle 타입에 대해 Summary 트레이트를 구현- 필수 메서드인
summarize
만 구현하고,default_summary
는 기본 구현을 사용
사용 예시
실제 프로젝트에서 사용되는 예시를 살펴보자:
pub trait Tensor {
fn shape(&self) -> &[usize];
fn dtype(&self) -> DataType;
fn numel(&self) -> usize {
self.shape().iter().product()
}
fn is_cuda(&self) -> bool {
false
}
}
struct CPUTensor<T> {
data: Vec<T>,
shape: Vec<usize>,
dtype: DataType,
}
impl<T> Tensor for CPUTensor<T> {
fn shape(&self) -> &[usize] {
&self.shape
}
fn dtype(&self) -> DataType {
self.dtype
}
}
위 코드는 ML 프레임워크에서 텐서를 표현하는 트레이트와 그 구현체의 일부이다.
Tensor
트레이트는 모든 텐서 타입이 가져야 할 공통 기능을 정의한다:
- 필수 메서드:
shape
: 텐서의 차원 정보를 반환dtype
: 텐서의 데이터 타입을 반환
- 기본 구현이 제공되는 메서드:
numel
: 텐서의 전체 원소 개수를 계산is_cuda
: GPU 텐서인지 여부를 반환 (기본값은 false)
트레이트를 사용하면 어떤 장점이 있을까?
fn print_tensor_info<T: Tensor>(tensor: &T) {
println!("Shape: {:?}", tensor.shape());
println!("Total elements: {}", tensor.numel());
println!("Data type: {:?}", tensor.dtype());
println!("Is CUDA: {}", tensor.is_cuda());
}
위 함수는 트레이트의 장점을 잘 보여준다:
- 추상화: 구체적인 텐서 구현에 상관없이 공통 인터페이스를 통해 접근
- 코드 재사용: 다양한 텐서 타입에 대해 같은 함수를 사용 가능
- 확장성: 새로운 텐서 타입을 추가할 때 Tensor 트레이트만 구현하면 됨
트레이트 바운드와 제네릭
트레이트는 제네릭 프로그래밍에서 특히 유용하다:
fn add<T: Tensor>(a: &T, b: &T) -> Box<dyn Tensor>
where
T: std::ops::Add<Output = T>,
{
todo!()
}
이 예시는 다음을 보여준다:
- 트레이트 바운드를 통한 타입 제약
- where 절을 사용한 복잡한 제약조건 명시
- 동적 디스패치를 위한 트레이트 객체 사용
정리
러스트의 트레이트는 다음과 같은 핵심적인 기능을 제공한다:
- 추상화: 공통 인터페이스 정의를 통한 코드 추상화
- 타입 안전성: 컴파일 시점에 타입 검사를 통한 안전성 보장
- 코드 재사용: 다양한 타입에 대해 동일한 동작을 정의
- 확장성: 새로운 타입에 대한 쉬운 확장 가능
트레이트는 러스트의 타입 시스템에서 핵심적인 역할을 하며, 안전하고 유연한 코드를 작성하는데 필수적인 도구이다. 적절한 트레이트 설계와 활용은 러스트 프로그래밍의 중요한 기술이다.