Generic Types, Traits, and Lifetimes

  • Generic Type : 구체화된 타입을 추상화한 대체 타입 (보통 함수 파라미터로 활용)
  • Trait : Generic과 결합하여 특정 동작을 하는 타입으로 제한하는 역할
  • Lifetime : Generic의 일종으로 컴파일러에게 참조자들의 관계에 대해 정보 전달

Generic 적용해보기 1 - 전

fn largest(list: &[i32]) -> i32 {
    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 numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];

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

Generic 적용해보기 1 - 후

fn largest<T>(list: &[T]) -> T { # 여기 주목
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {      # binary operation `>` cannot be applied to type `T`
            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);
}

Generic 적용해보기 2 - 전

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let wont_work = Point { x: 5, y: 4.0 }; # expected integral variable, found
}

Generic 적용해보기 2 - 후

struct Point<T, U> {
    x: T,
    y: U, # 다른 타입을 가질수 있게 정의됨
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

Generic 적용해보기 3 (함수)

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

Generic 적용해보기 4 (mixup)

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {

    # impl에 선언한 것과 다른 타입 parameter를 받는 함수
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y, # mixup
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' }; # 다른 타입

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

Generic 코드 성능

  • 런타임 비용은 없음
  • 컴파일 타임에 monomorphization를 통해 Generic을 명시적인 타입으로 치환함
# 컴파일 전
let integer = Some(5);
let float = Some(5.0);

# 컴파일 후
enum Option_i32 {
    Some(i32),
    None,
}
enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

Traits: Defining Shared Behavior

기본 문법

pub trait Summary {
    fn summarize(&self) -> String;
}

Implementing a Trait on a Type

  • 일반 함수를 구현할때와 비슷하게 impl 키워드를 사용함
  • 뒤에 for 키워드가 따라온다는게 차이점
pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

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

Default Implementations

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {             # Default 구현에 있으면 추후 선언 없이 사용가능
        format!("(Read more from {}...)", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {      # Overriding 가능
        format!("@{}", self.username)
    }
}

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.summarize()); # 여기 주목

Traits as Parameters

  • 인터페이스처럼 사용 가능
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

Trait Bound Syntax

  • Generic을 활용해 특성 범위를 명시
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
  • +를 활용한 명시
pub fn notify(item: &(impl Summary + Display)) {
# 또는
pub fn notify<T: Summary + Display>(item: &T) {

Trait Bound Syntax

  • Generic을 활용해 특성 범위를 명시
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
  • +를 활용한 명시
pub fn notify(item: &(impl Summary + Display)) {
# 또는
pub fn notify<T: Summary + Display>(item: &T) {

Clearer Trait Bounds with where Clauses

  • 가독성 개선을 위해 where절을 쓸 수 있음
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{

Using Trait Bounds to Conditionally Implement Methods

use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}