[Chapter 10-2] Rust 트레잇(Trait)

hwwwa·2021년 12월 15일
0

🦀 Rust

목록 보기
25/25

Trait - 공유 동작 정의

  • 타입들이 공통적으로 갖는 동작에 대해 추상화 가능하도록 함
  • 컴파일러에게 특정한 타입이 갖고 다른 타입들과 함께 공유할 수도 있는 기능
  • generic type parameter를 사용할 때, 컴파일 타임에 해당 generic type이 어떤 trait을 구현한 타입이어야 함을 명시하여 원하는 동작을 하도록 trait bounds(트레잇 바운드) 사용

Trait 정의

pub trait Summarizable {
	fn summary(&self) -> String;
}
  • summary 메소드에 의해 제공되는 동작으로 구성된 Summarizable trait의 정의
  • 중괄호 내에는 해당 trait을 구현하는 타입들이 가질 필요가 있는 동작들을 묘사한 메소드 시그니처들을 정의
  • 위 코드는 메소드 시그니처 뒤에 중괄호 내의 정의부를 제공하는 대신 세미콜론을 넣어 trait을 구현하는 각 타입이 메소드의 본체에 대한 해당 타입 고유의 커스텀 동작 제공 대신 어떤 타입이든 메소드 summary를 정확히 동일한 시그니처로 정의되도록 강제함
  • trait은 한 줄 당 하나의 메소드 시그니처와 각 줄 끝에 세미콜론을 갖도록 하여 본체 내에 여러 개의 메소드를 가질 수 있음

특정 타입에 대한 Trait 구현

  • lib.rs
pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summarizable for NewsArticle {
    fn summary(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summarizable for Tweet {
    fn summary(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
  • main.rs
let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
};

println!("1 new tweet: {}", tweet.summary());
  • Summarizable trait과 NewsArticleTweet 타입을 동일한 [lib.rs](http://lib.rs) 내에 정의했기 때문에 모두 동일한 스코프 내에 있음
  • 만약 lib.rsaggregator라고 불리는 crate에 대한 것이고 추가로 WeatherForecast 구조체에 대해 Summarizable을 구현하길 원한다면 Summarizable trait을 스코프로 가져와야 함
extern crate aggregator;

use aggregator::Summarizable;

struct WeatherForecast {
    high_temp: f64,
    low_temp: f64,
    chance_of_precipitation: f64,
}

impl Summarizable for WeatherForecast {
    fn summary(&self) -> String {
        format!("The high will be {}, and the low will be {}. The chance of
        precipitation is {}%.", self.high_temp, self.low_temp,
        self.chance_of_precipitation)
    }
}
  • 앞서 Summariable trait을 정의할 때 pub 키워드를 사용했기 때문에 Summariable은 공개 트레잇
  • 트레잇 혹은 타입이 내부 크레이트일 경우에만 해당 타입에서의 트레잇 정의 가능
  • Vec에 대한 Display 트레잇 구현 불가능. 둘 다 표준 라이브러리 내에 정의되어 있기 때문
  • aggregator crate 기능의 일부로서 Tweet과 같은 커스텀 타입에 대한 Display 트레잇 구현은 가능. Vec에 대한 Summarizable 구현 또한 가능 ⇒ orphan rule (고아 규칙. 부모 타입이 존재하지 않기 때문)
  • Rust가 orphan rule을 강제하기 때문에 두 크레이트가 동일 타입에 대해 동일한 트레잇을 구현할 수 없음. 코드가 망가질 위험이 없어짐

기본 구현

  • 특정한 타입에 대한 trait 구현 시, 각 method들에 대한 기본 동작을 유지하거나 override 하도록 선택 가능
pub trait Summarizable {
    fn summary(&self) -> String {
        String::from("(Read more...)")
    }
}
  • 기본 구현 사용 시 빈 impl 블록을 명시
impl Summarizable for NewsArticle {}
  • 기본 구현은 동일한 트레잇 내의 다른 메소드들을 호출하는 것이 허용되고 해당 메소드들이 기본 구현을 갖고 있지 않아도 됨
  • 아래의 경우에는 어떤 타입에 대한 해당 트레잇 구현 시 author_summary만 정의하면 됨
pub trait Summarizable {
    fn author_summary(&self) -> String;

    fn summary(&self) -> String {
        format!("(Read more from {}...)", self.author_summary())
    }
}
impl Summarizable for Tweet {
    fn author_summary(&self) -> String {
        format!("@{}", self.username)
    }
}

Trait Bound

  • Generic Type Parameter를 이용하는 Trait 사용 가능
  • 아래 코드는 파라미터 item 상에서 summary 메소드를 호출 하는 함수 notify를 정의한 것. 이때 item은 Generic Type T
pub fn notify<T: Summarizable>(item: T) {
    println!("Breaking news! {}", item.summary());
}
  • 에러 없이 item 상에서 summary를 호출하기 위해서 T에 대한 Trait Bound를 사용해 item이 반드시 Summarizable 트레잇을 구현한 타입이어야 함을 특정
  • + 사용 시 하나의 Generic Type에 대해 여러 개의 Trait Bound 특정 가능
  • 만약 Trait Bound T: Summarizable + Display라면 TSummariableDisplay 둘 다 구현한 어떤 타입이어야 함을 의미
  • 여러 개의 Generic Type Parameter를 가질 때, 각 Generic은 고유의 trait bound를 가짐
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
  • 위와 같이 표현할 수도 있지만 읽기 편하도록 함수 시그니처 뒤에 where 절 사용 가능
fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

Trait Bound를 사용해 largest 함수 고치기

  • 10-1장에서 작성한 largest 함수에 trait bound를 사용해 에러를 고쳐봅시다.
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

실행결과:

error[E0369]: binary operation `>` cannot be applied to type `T`
  |
5 |         if item > largest {
  |            ^^^^
  |
note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
  • 타입 T의 두 값을 비교하는 연산자는 표준 라이브러리 트레잇인 std::cmp::PartialOrd 상에 기본 메소드로 정의되어 있으므로 T에 대한 trait bound 내에 PartialOrd 특정 필요
  • 고정된 크기를 갖는 i32char와 같은 타입들은 스택에 저장될 수 있으며 이 타입들은 Copy 트레잇을 구현하고 있음
  • 만약 오직 Copy가 구현된 타입들을 가지고 호출하도록 가정한다면 T의 trait bound에 Copy 또한 추가
use std::cmp::PartialOrd;

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];

    let result = largest(&numbers);
    println!("The largest number is {}", result);

    let chars = vec!['y', 'm', 'a', 'q'];

    let result = largest(&chars);
    println!("The largest char is {}", result);
}
  • 만약 Copy trait을 구현한 타입에 대한 것으로 제한하길 원치 않는다면, T가 Copy 대신 Clone trait bound를 갖도록 명시하여 함수가 소유권을 갖도록 할 수 있음
    • clone 함수 사용은 더 많은 힙 할당을 할 수 있어 많은 양의 데이터에 대해서 동작이 느릴 수 있음을 주의
  • 함수가 슬라이스 내의 T 값에 대한 참조자(&T)를 반환하도록 할 수도 있음
    • Clone이나 Copy trait bound가 필요하지 않으며 어떠한 힙 할당도 하지 않게 됨

0개의 댓글