티스토리 뷰

vector는 한번에 하나의 타입만 보관할 수 있다.

만약 라이브러리 사용자가 특정 상황에서 유효한 타입 묶음을 확장할 수 있도록 하길 원할 수 있다.

예를 들어보자

아이템 리스트에 걸쳐 각각에 대하여 draw메소드를 호출하여 이를 화면에 그리는 GUI 도구를 활용할 수 있다.

GUI도구를 만든다는 가정을 해보자.

draw라는 메소드를 가지고 있는 component라는 객체를 정의한다. 다른 클래스들은 Component를 상속받고 draw메서드를 물려받게 된다. 이들은 각각 draw 메소드를 오버라이딩하여 고유동작으로 정의할 수 있으나, framework는 모든 유형을 마치 Component인 것 처럼 다룰 수 있고 draw를 호출할 수 있다.

 rust는 상속이 없으므로 사용자들이 새로운 타입을 정의하고 확장하도록 할 필요가 있다.

 

공통 동작 trait정의 

 gui가 갖길 원하는 동작을 구현하기 위해 draw라는 이름의 메서드 하나를 갖는 Draw trait을 정의한다.

그러면 trait객체를 취하는 vector를 정의할 수 있다.

pub trait Draw {
    fn draw(&self);
}

 

그 다음 component vector를 보유한 Screen을 정의한다.

pub struct Screen {
    pub components: Vec<Box<Draw>>,
}

Screen 구조체에서는 각 component마다 draw 메서드를 호출하는 run 메서드를 정의한다.

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

trait 바운드와 함께 제네릭 타입 파라미터를 사용하는 구조체를 정의하는 것과는 다르게 동작한다.

제네릭 타입 파라미터는 합넙에 하나의 구체 타입으로만 대입될 수 있는 반면, trait 객체를 사용하면 런타임에 여러 구체 타입을 트레잇 객체에 대하여 채워넣을 수 있게 된다.

만약 제네릭 타입과 trait바운드를 정의하여 구조체를 정의해봤을때,

 

pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T> where T: Draw {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

Vector의 타입에 대하여 동일한 타입만 허용이 된다.

동일한 타입만 정의한다면 구조체, 제네릭과 특정 범위를 사용하는것이 바람직하다. (구체 타입을 사용하기 위해 컴파일 타임에 단형성화, monmorphize 되기 때문이다.)

 반면 trait 객체를 사용하는 메서드를 이용할때는 하나의 Screen 인스턴스가 Box<Button> 혹은 Box<TextField>도 담을 수 잇는 Vec<T>를 보유할 수 있다.

 

Trait 구현

 Draw trait에 Button타입을 추가한다.

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        // code
    }
}

Button의 width, height, label 필드는 다른 컴포넌트와는 차이가 있다.

TextField 타입을 예로 들면, 이 필드들에 추가로 place holder 필드를 소유하게 될것이다.

화면에 그리고자 하는 각각의 타입은 Draw trait을 구현할테지만, 해당 타입을 그리는 방법을 정의하기 위하여 draw메서드 내 서로 다른 코드를 사용하게 될 것다.

Button타입은 impl블록 내 사용자가 버튼을 클릭했을 때 어떤 일이 벌어질지 관련된 메서드를 포함할수도있으며, TextField는 이러한 타입을 적용할 수 없다.

 

만약 라이브러리 사용자가 width, height 및 options 필드가 있는 Selectbox구조체를 구현하기로 했다면, Selectbox타입에도 Draw trait을 구현해야 한다.

extern crate gui;
use gui::Draw;

struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        // code to actually draw a select box
    }
}

라이브러리 사용자는 이제 Screen 인스턴스를 만들기 위해 main함수를 구현할 수 있다.

Screen인스턴스에는 Selectbox와 Button이 trait객체가 되도록 하기 위해 Box<T> 내에 할당함으로서, 이들을 추가할 수 있다. 

Screen 인스턴스에는 Selectbox와 button이 trait객체가 되도록 하기 위해 Box<T>내에 넣음으로써 이들을추가할 수 있다.

그러면 Screen 인스턴스 상의 run메서드를 호출할 수 있는데, 이는 각 컴포넌트들에 대하여 draw를 호출할 것이다.

use gui::{Screen, Button};

fn main() {
    let screen = Screen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No")
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("OK"),
            }),
        ],
    };

    screen.run();
}

GUI 라이브러리를 만들때는 누군가 Selectbox타입을 추가할 수 있는지 몰랐는데, Screen struct는 새로운 타입에 대해서도 동작하고 이를 그려낼 수 있었다. Selectbox가 Draw타입을 구현했고, 이는 draw메서드가 구현되어 있음을 의미한다.

이러한 개념은 동적 타입 언어의 Ducks typing이라고 하는 개념과 유사하다. 

 

Ducks typing을 사용하는 코드와 유사한 코드를 작성하기 위하여 trait객체와 rust의 타입 시스템을 사용하는 것의 장점은 어떤값이 특정한 메서드를 구현했는지를 검사해야 하거나, 값이 메서드를 구현하지 않았는데 호출한다면 생길 수 있는 에러에 대한 걱정을 할필요가 없다는 것이다.

아래의 코드를 실행시켜보자.

extern crate gui;
use gui::Screen;

fn main() {
    let screen = Screen {
        components: vec![
            Box::new(String::from("Hi")),
        ],
    };

    screen.run();
}

트레잇 객체의 트레잇을 구현하지 않은 타입의 사용을 시도하였다.

아래와 같은 오류가 발생할 것이며 String이 Draw메서드를 구현하지 않았으므로 사용할 수 없다.

오류 내용을 보면 Draw에 대한 trait bound가 되지 않은 String을 넘겨주려고 하고 있다고 하며, String에 대하여 Draw를 구현하여야 한다고 한다.

 

제네릭에 trait바운드를 사용했을 때 컴파일러에 의해 이뤄지는 단형성화 프로세스의 실행에 대하여, 제네릭 타입 파라미터를 사용한 각각의 구체 타입을 위한 함수와 메서드의 제네릭 없는 구현체를 생성한다. monmorphize 로부터 야기된 코드는 정적 디스패치(static dispatch)를 수행하는데, 이는 호출하고자 하는 메서드가 어떤 것인지 컴파일 시점에 알고 있어야 한다는 것이다. 

반대로 동적 디스패치(dynamic dispatch)가 있는데, 호출하는 메서드를 컴파일 타임에 알수 없는 경우 수행된다.

이 경우, 컴파일러는 런타임에 어떤 메서드가 호출되는지 알아내는 코드를 생성한다.

trait 객체는 동적 디스패치를 수행한다.

컴파일러는 trait 객체를 사용중인 코드와 함께 사용될 수 있는 모든 타입을 알지 못하기 때문에, 어떤 타입에 구현된 어떤 메서드를 호출할 지 알지 못한다.

대신 trait객체 내에 존재하는 포인터를 사용하여 어떤 메서드가 호출될 지 런타임에 알아낸다. 

 

trait 객체에 대하여 객체 안전성 요구

객체 안전한 trait만 trait객체로 만들 수 있다. trait객체를 안전하게 만드는 복잡한 규칙이 있지만 2가지 주요 규칙만 알아보자.

 어떤 trait 내의 모든 메서드들이 다음과 같은 속성들을 가지고 있다면, 해당 trait은 객체 안전하다.

  • 반환값의 타입이 Self가 아니다.
  • 제네릭 타입 매개변수가 없다.

Self키워드는 trait 혹은 메서드를 구현하고 있는 타입의 별칭이다. trait객체가 반드시 객체 안전해야 하는 이유는 trait  객체를 사용하면, rust가 trait에 구현된 구체(concrete) 타입을 알 수 없기 때문이다.

만약 trait 메서드가 고정된 Self 타입을 반환하는데 trait 객체는 Self의 정확한 타입을 잊게 된다면, 메서드가 원래 구체 타입을 사용할 수 있는 방법이 없다.

trait을 사용할 때 구체 타입 parameter로 채워지는 제네릭 타입도 마찬가지이다. 그 구체 타입들은 해당 trait을 구현하는 타입의 일부가 된다. trait객체 사용을 통해 해당 타입을 잊게 된다면, 제네릭 타입 parameter를 채울 타입을 알 수 없게 된다.

 

메서드가 객체 안전하지 않은 trait의 예시는 표준 라이브러리의 Clone trait이다.

pub trait Clone {
    fn clone(&self) -> Self;
}

String 타입은 Clone trait을 구현하고, String 인스턴스에 대하여 clone 메서드를 호출하면, String메서드를 반환받을 수 있다.

비슷하게 Vec<T>의 인스턴스 상 clone을 호출하면, Vec<T> 인스턴스를 얻을 수 있다.

clone 선언은 Self에 어떤 타입이 사용되는지 알필요가 있다. Self가 반환타입이기 때문이다.

 

컴파일러는 trait객체와관련하여 객체 안전성 규칙에 위배되는 시도를 발견하면 알려준다.

Screen구조체에 Draw trait 대신 Clone trait을 구현하려고 시도해 봤다.

pub struct Screen {
    pub components: Vec<Box<Clone>>,
}

Clone을 trait객체로 사용할 수 없다고 한다.

 

https://rinthel.github.io/rust-lang-book-ko/ch17-02-trait-objects.html

 

트레잇 객체를 사용하여 다른 타입 간의 값 허용하기 - The Rust Programming Language

8장에서는 벡터가 한 번에 하나의 타입만 보관할 수 있다는 제약사향이 있다고 언급했습니다. 우리가 만들었던 Listing 8-10의 작업내역에서는 정수, 부동소수점, 그리고 문자를 보관하기 위한 varia

rinthel.github.io

 

'Rust-Language' 카테고리의 다른 글

매크로  (0) 2021.12.02
life time, 라이프 타임  (0) 2021.09.12
trait: 공유 동작 정의  (0) 2021.09.08
반복자, iterator  (0) 2021.09.03
Closure, 클로저 함수  (0) 2021.08.05
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함