---
# System prepended metadata

title: 12차 러스트 스터디

---

# 12차 러스트 스터디

## 담당
- 발표: 이명수 님
- 리뷰: 이지한 님
- 서기: 김기덕 님

## 과제 리뷰
### [멀티 스레드 활용해보기](https://github.com/JannLee/rust-study)

## 17. 러스트의 객체 지향 프로그래밍 기능들

- 러스트에서 객체 지향을 어떻게 다룰 것인가!

### 17.1. 객체 지향 언어의 특성
- 캡슐화, 상속
- 러스트는 상속이 따로 없고 trait을 사용해 상속의 동작을 구현할 수 있다.

#### 객체는 데이터의 동작을 담습니다.
- 아래 정의에 따르면, 러스트는 객체 지향적(구조체, 열거형)

```
객체-지향 프로그램은 객체로 구성된다. 객체는 데이터 및 이 데이터를 활용하는 프로시저를 묶는다. 이 프로시저들은 보통 메소드 혹은 연산 (operation) 으로 불린다.
```

#### 상세 구현을 은닉하는 캡슐화

- 캡슐화를 활용하면 리팩토링에 좋다.
- 일반적으로 OOP와 관련된 또다른 면은 캡슐화로, 그 의미는 객체를 이용하는 코드에서 그 객체의 상세 구현에 접근할 수 없게 한다. 
    - 유일하게 객체와 상호작용하는 방법은 이것의 공개 API를 통하는 것
    - 러스트에서는 기본 비공개, `pub 키워드를 사용`하여 공개 여부를 결정한다.

#### 타입 시스템과 코드 공유로서의 상속
 - 상속을 직접적으로 지원하지는 않는다. 
 - trait을 사용해 다형성을 구현했다. 
 - trait을 오버라이딩해 기능을 재정의 할 수 있다.

### 17.2. 트레잇 객체를 사용하여 다른 타입 간의 값 허용하기
 - trait을 사용해 다형성을 구현하는 내용
 - 다른 언어의 인터페이스와 유사
 - draw 메소드를 호출하여 이를 화면에 그리는 그래픽 유저 인터페이스(GUI) 도구 제작

#### 공통된 동작을 위한 트레잇 정의하기

- 제네릭 타입 파라미터는 한 번에 하나의 구체 타입으로만 대입될 수 있는 반면, 트레잇 객체를 사용하면 런타임에 여러 구체 타입을 트레잇 객체에 대해 채워넣을 수 있습니다.

```rust
// 제네릭 타입 예시
// 이렇게하면 전부 Button 타입 혹은 전부 TextField 타입인 컴포넌트 리스트를 가지는 Screen 인스턴스로 제한
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();
        }
    }
}
```

```rust=
// 트레잇 객체 사용 예시
// 트레잇 객체를 사용하는 메소드를 이용할때는 하나의 Screen 인스턴스가 Box<Button> 혹은 Box<TextField>도 담을 수 있는 Vec<T>를 보유할 수 있습니다.
pub struct Screen {
    pub components: Vec<Box<Draw>>,
}
```

#### 트레잇 구현하기

- Draw 트레잇을 구현하는 Button 구조체 예시
    - Draw 트레잇을 구현하여 draw 메소드를 마치 상속한 것처럼 구현
```rust
pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        // code to actually draw a button
    }
}
```

- components 벡터에 담기는 값의 타입을 Box\<Draw>로 특정함으로서 우리는 draw 메소드를 호출할 수 있는 값을 요구하는 Screen을 정의
    - `이러한 개념 —값의 구체적인 타입이 아닌 값이 응답하는 메시지 만을 고려하는 개념—` 은 동적 타입 언어들의 duck typing 개념과 유사
```rust
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();
}
```

#### 트레잇 객체는 동적 디스패치를 수행합니다
- 동적 바인딩, 정적 바인딩과 비슷한 개념으로 러스트에서는 동적, 정적 디스패치를 수행한다 

#### 트레잇 객체에 대하여 객체 안전성이 요구됩니다.
- 안전한 객체만 트레잇 객체로 만들 수 있다. 
- 안전한 객체
    - 반환값의 타입이 Self가 아닌 객체.
    - 제네릭 타입 매개변수가 없는 객체.
- 객체가 안전해야하는 이유
    - self 타입을 명확히 알수 없어 컴파일 오류.
- 안전하지 않은 트레잇 메소드 예시
    - Clone트레잇의 clone메소드
```rust
pub trait Clone {
    fn clone(&self) -> Self;
}
```
### 17.3. 객체 지향 디자인 패턴 구현하기

- state pattern 연습
    - 점진적인 방식으로 블로그에 게시물을 올리는 작업 흐름을 구현
    - 상태 패턴을 사용한다는 것은 상태를 보유한 값의 코드 혹은 그밧을 사용하는 코드는 변경될 필요가 없음을 의미.

- blog 크레이트가 갖길 원하는 요구 동작들을 보여주는 코드
    - 상태에 따라 content 메소드 출력이 다름을 볼 수 있음
```rust
extern crate blog;
use blog::Post;

fn main() {
    let mut post = Post::new();

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());

    post.request_review();
    assert_eq!("", post.content());

    post.approve();
    assert_eq!("I ate a salad for lunch today", post.content());
}
```

#### Post를 정의하고 초안 상태의 새 인스턴스 생성하기

- State 트레잇은 게시물의 상태 변화에 따라 달라지는 동작을 정의하고, `Draft`, `PendingReview`, 그리고 `Published` 상태는 모두 State 트레잇을 구현

```rust
pub struct Post {
    state: Option<Box<State>>,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }
}

trait State {}

struct Draft {}

impl State for Draft {} // Draft에서 State 트레잇 구현
```

#### 게시물 콘텐츠의 글을 저장하기

- content의 String 상에서 push_str을 호출하고 text를 인자로 전달해 저장된 content에 추가
    - add_text 메소드는 가변 참조자 self를 취하는데, 그 이유는 우리가 add_text를 호출하고 있는 해당 Post 인스턴스를 변경하기 때문
```rust
pub struct Post {
    content: String,
}

impl Post {
    // --snip--
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
}
```

#### 초안 게시물의 내용이 비어있음을 보장하기

- 항상 비어있는 스트링 슬라이스를 반환하는 Post의 content 메소드에 대한 껍데기 구현

```rust
impl Post {
    // --snip--
    pub fn content(&self) -> &str {
        ""
    }
}
```

#### 게시물에 대한 리뷰 요청이 그의 상태를 변경합니다

- State 트레잇에 request_review 메소드를 추가
    - 트레잇을 구현하는 모든 타입은 이제 request_review 메소드를 구현 필요

```rust
impl Post {
    // --snip--
    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }
}

trait State {
    fn request_review(self: Box<Self>) -> Box<State>;
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<State> {
        Box::new(PendingReview {}) // 리뷰 요청으로 인한 상태 변경
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<State> {
        self // pending 상태에서는 상태 변경이 필요 없음
    }
}
```


#### content의 동작을 변경하는 approve 메소드 추가하기
- approve 메소드를 통하여 state를 설정.
    - approve 메소드를 State 트레잇에 추가
    - State를 구현하는 새 구조체 Published 상태도 추가
```rust
#![allow(unused)]
fn main() {
pub struct Post {
    state: Option<Box<State>>,
    content: String,
}

impl Post {
    // --snip--
    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    fn request_review(self: Box<Self>) -> Box<State>;
    fn approve(self: Box<Self>) -> Box<State>;
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<State> {
        Box::new(PendingReview {})
    }

    // --snip--
    fn approve(self: Box<Self>) -> Box<State> {
        self
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<State> {
        self
    }

    // --snip--
    fn approve(self: Box<Self>) -> Box<State> {
        Box::new(Published {})
    }
}

struct Published {}

impl State for Published {
    fn request_review(self: Box<Self>) -> Box<State> {
        self
    }

    fn approve(self: Box<Self>) -> Box<State> {
        self
    }
}
```
- Post의 content 메소드를 갱신
    - 상태가 Published이면, 우리는 게시물의 content 필드의 값을 반환.
    - 그렇지 않다면, 우리는 빈 스트링 슬라이스를 반환
```rust
#![allow(unused)]
fn main() {
trait State {
    fn content<'a>(&self, post: &'a Post) -> &'a str;
}
pub struct Post {
    state: Option<Box<State>>,
    content: String,
}

impl Post {
    // --snip--
    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(&self)
    }
    // --snip--
}
}

```
- 우리가 &Box\<State\>의 content를 호출할 때, 역참조 강제는 &와 Box에 영향을 줘서 content 메소드가 궁극적으로 State 트레잇을 구현하는 타입 상에서 호출
    - 우리는 빈 스트링 슬라이스를 반환하는 content 메소드의 기본 구현을 추가. 
    - Published 구조체는 content 메소드를 오버라이딩하고 post.content의 값을 반환.
```rust
#![allow(unused)]
fn main() {
pub struct Post {
    content: String
}
trait State {
    // --snip--
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }
}

// --snip--
struct Published {}

impl State for Published {
    // --snip--
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}
}

  ``` 
#### 상태 패턴의 기회비용

- 상태 패턴을 사용하면 추가 기능을 구현하기 쉬움
    - 상태 패턴을 사용하지 않으면 상태가 추가될 때마다 여러 분기에 대한 케이스가 늘어남
    - 상태 패턴을 사용하지 않으면 결과를 이해하기 위해 여러 곳을 전부 봐야함
- 상태 패턴 단점
    - 상태가 상태 간의 전환을 구현하기 때문에, 몇몇 상태들이 서로 묶이게 됨
        - 만약 우리가 PendingReview와 Published 사이에 Scheduled와 같은 상태를 추가하면, PendingReview에서 Scheduled로 전환되도록 코드를 변경해야 함
    - 몇몇 로직을 중복

- 상태와 동작을 타입처럼 인코딩하기
    - 이제 프로그램은 모든 게시물이 초안 게시물로 시작되고, 초안 게시물들은 그들의 내용을 출력할 능력이 없음을 보장
    - 이 제약사항을 벗어나는 어떤 시도라도 컴파일러 에러로 끝나게 됨

```rust
pub struct Post {
    content: String,
}

pub struct DraftPost {
    content: String,
}

impl Post {
    pub fn new() -> DraftPost {
        DraftPost {
            content: String::new(),
        }
    }

    pub fn content(&self) -> &str {
        &self.content
    }
}

impl DraftPost {
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
}
```

- 다른 타입으로 변환하는 것처럼 전환 구현하기

```rust
impl DraftPost {
    // --snip--

    pub fn request_review(self) -> PendingReviewPost {
        PendingReviewPost {
            content: self.content,
        }
    }
}

pub struct PendingReviewPost {
    content: String,
}

impl PendingReviewPost {
    pub fn approve(self) -> Post {
        Post {
            content: self.content,
        }
    }
}
```


### 17장 정리

- 트레잇 객체를 사용하여 몇가지 객체 지향 기능을 러스트 내에서 사용할 수 있다

## 다음 시간

### 스터디 범위

- 18. 값의 구조와 매칭되는 패턴

### 스터디 과제

- 여러 디자인 패턴 중 하나를 골라서 구현해보기


## 벌금현황
- 이지한: 0
- 김기덕: 0
- 유병조: 0
- 이명수: 0
- 이재현: 0
- 정연집: 0
- 허신: 10,000

## 회고
- 이지한: 못 올뻔 했는데, 다행히 참석했네요. crate 은 좀더 봐야겠어요.
- 김기덕: 서기를 처음하는데 힘드네요..ㅠ 앞으로 서기하시는 분들 많이 도와드리겠습니다...!
- 유병조: 객체 안전성 부분이 좀 헷갈려서 따로 찾아봐야겠네요
- 이명수: 다음부터는 2번씩 읽어 보고 와야겠어요 ㅎㅎ
- 이재현: 트레잇 부분은 볼때마다 잘 안와닿네요.. 
- 정연집: 앞에 트레잇부분 다시 봐야할 것 같습니다.
- 허신: 