Rust Programming | 트레이트의 이해

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

Author: Han Damin
Tags: rust study trait
Published: 2024/12/07 06:18 Series: Rust

러스트에서 트레이트(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. 확장성: 새로운 타입에 대한 쉬운 확장 가능

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