Back
Blog Rust

Rust Programming | 트레이트의 이해

러스트의 트레이트(Trait)에 대해 자세히 알아보자

Han Damin
5 min read
rust study trait

러스트에서 트레이트(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 트레이트는 모든 텐서 타입이 가져야 할 공통 기능을 정의한다:

  1. 필수 메서드:
    • shape: 텐서의 차원 정보를 반환
    • dtype: 텐서의 데이터 타입을 반환
  2. 기본 구현이 제공되는 메서드:
    • 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());
}

위 함수는 트레이트의 장점을 잘 보여준다:

  1. 추상화: 구체적인 텐서 구현에 상관없이 공통 인터페이스를 통해 접근
  2. 코드 재사용: 다양한 텐서 타입에 대해 같은 함수를 사용 가능
  3. 확장성: 새로운 텐서 타입을 추가할 때 Tensor 트레이트만 구현하면 됨

트레이트 바운드와 제네릭

트레이트는 제네릭 프로그래밍에서 특히 유용하다:

fn add<T: Tensor>(a: &T, b: &T) -> Box<dyn Tensor>
where
    T: std::ops::Add<Output = T>,
{
    todo!()
}

이 예시는 다음을 보여준다:

  1. 트레이트 바운드를 통한 타입 제약
  2. where 절을 사용한 복잡한 제약조건 명시
  3. 동적 디스패치를 위한 트레이트 객체 사용

정리

러스트의 트레이트는 다음과 같은 핵심적인 기능을 제공한다:

  1. 추상화: 공통 인터페이스 정의를 통한 코드 추상화
  2. 타입 안전성: 컴파일 시점에 타입 검사를 통한 안전성 보장
  3. 코드 재사용: 다양한 타입에 대해 동일한 동작을 정의
  4. 확장성: 새로운 타입에 대한 쉬운 확장 가능

트레이트는 러스트의 타입 시스템에서 핵심적인 역할을 하며, 안전하고 유연한 코드를 작성하는데 필수적인 도구이다. 적절한 트레이트 설계와 활용은 러스트 프로그래밍의 중요한 기술이다.

Share this article

Back to top