OOP란 무엇인가?Rust는 OOP가 아니다메소드 캡슐화 하기선택적 노출을 통한 추상화다형성과 TraitTrait에 구현된 메소드Trait 상속동적 vs 정적 디스패치Trait 객체크기를 알 수 없는 데이터 다루기Generic 함수Generic 함수 줄여쓰기BoxGeneric 구조체 다시 보기The Rust Programming Language 링크의문점
OOP란 무엇인가?
OOP(Object-Oriented Programming)은 다음의 특징을 가진 프로그래밍 언어
- 캡슐화 (encapsulation) - 객체라 불리는 단일 유형의 개념적 단위에 데이터와 함수를 연결지음.
- 추상화 (abstraction) - 데이터와 함수를 숨겨 객체의 상세 구현 사항을 알기 어렵게 함.
- 다형성 (polymorphism) - 다른 기능적 관점에서 객체와 상호작용하는 능력.
- 상속 (inheritance) - 다른 객체로부터 데이터와 동작을 상속받는 능력.
Rust는 OOP가 아니다
- Rust는 상속이 불가능
메소드 캡슐화 하기
- 함수(메소드라고도 하는)가 연결된
struct
인 객체라는 개념을 지원
- 모든 메소드의 첫번째 매개변수는 메소드와 연관된 인스턴스의 참조
&self
- 인스턴스에 대한 immutable한 참조.&mut self
- 인스턴스에 대한 mutable한 참조.
- 메소드는
impl
키워드 블록 안에 정의
impl MyStruct { ... fn foo(&self) { ... } }
struct SeaCreature { noise: String, } impl SeaCreature { fn get_sound(&self) -> &str { &self.noise } } fn main() { let creature = SeaCreature { noise: String::from("blub"), }; println!("{}", creature.get_sound()); }
선택적 노출을 통한 추상화
- Rust는 객체의 내부 동작을 숨길 수 있음
- 필드와 메소드들은 그들이 속한 모듈에서만 접근 가능
pub
키워드는 struct의 필드와 메소드를 모듈 밖으로 노출
모듈에 대한 내용은 9장 - 프로젝트 구성과 구조 참고
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; println!("{}", creature.get_sound()); }
다형성과 Trait
- Rust는
trait
으로 다형성 지원 (타 언어의 interface와 유사)
- trait 안에 메소드 원형을 정의
trait MyTrait { fn foo(&self); ... }
- struct의 구현된 trait 메소드들은 impl 블록 안에 정의
impl MyTrait for MyStruct { fn foo(&self) { ... } ... }
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; creature.make_noise(); }
Trait에 구현된 메소드
- trait에 메소드를 구현해 넣을 수 있음
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); fn make_alot_of_noise(&self) { self.make_noise(); self.make_noise(); self.make_noise(); } } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; creature.make_alot_of_noise(); }
Trait 상속
trait은 다른 trait의 메소드들을 상속 받을 수 있음
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); } trait LoudNoiseMaker: NoiseMaker { // NoiseMaker 상속 fn make_alot_of_noise(&self) { self.make_noise(); self.make_noise(); self.make_noise(); } } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } impl LoudNoiseMaker for SeaCreature {} fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; creature.make_alot_of_noise(); }
동적 vs 정적 디스패치
메소드는 다음의 두 가지 방식으로 실행
- 정적 디스패치 (static dispatch) - 인스턴스의 자료형을 알고 있는 경우, 어떤 함수룰 호출해야 하는지 정확히 알고 있음
- 동적 디스패치 (dynamic dispatch) - 인스턴스의 자료형을 모르는 경우, 올바른 함수를 호출할 방법을 찾아야 함
&dyn MyTrait
은 동적 디스패치를 통해 객체의 인스턴스들을 간접적으로 작동동적 디스패치는 trait 자료형 앞에
dyn
을 붙일 것을 권고struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } fn static_make_noise(creature: &SeaCreature) { // 실제 자료형을 압니다 creature.make_noise(); } fn dynamic_make_noise(noise_maker: &dyn NoiseMaker) { // 실제 자료형을 모릅니다 noise_maker.make_noise(); } fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; static_make_noise(&creature); dynamic_make_noise(&creature); }
메모리 상세
동적 디스패치는 실제 함수 호출을 위한 포인터 추적으로 인해 느릴 수 있음
Trait 객체
- trait 객체:
&dyn MyTrait
자료형을 가진 매개변수
- trait 객체는 인스턴스의 올바른 메소드를 간접적으로 호출
- 인스턴스에 대한 포인터와 인스턴스 메소드들에 대한 함수 포인터 목록을 갖고있는 struct
- C++에서는
vtable
이라고 함
크기를 알 수 없는 데이터 다루기
- trait은 원본 struct의 원래 크기를 알기 어려움
- Rust에서 크기를 알 수 없는 값이 struct에 저장될 때는 다음의 두 가지 방법으로 처리
generics
- 매개변수의 자료형을 효과적으로 활용하여 알려진 자료형 및 크기의 struct/함수를 생성indirection
- 인스턴스를 heap에 올림으로써 실제 자료형의 크기 걱정 없이 그 포인터만 저장할 수 있는 간접적인 방법을 제공
Generic 함수
- Rust의 generic은 trait과 함께 작동
- 매개변수 자료형
T
를 정의할 때 해당 인자가 어떤 trait을 구현해야 하는지 나열
- 아래 예제에서
T
자료형은Foo
trait을 반드시 구현해야 함
fn my_function<T>(foo: T) where T:Foo // Foo trait을 구현해야함 { ... }
- generic을 이용하면 컴파일 시 자료형과 크기를 알 수 있음 (정적 자료형 함수 생성)
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } fn generic_make_noise<T>(creature: &T) where T: NoiseMaker, { // 컴파일 타임에 실제 자료형을 알게 됩니다 creature.make_noise(); } fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; generic_make_noise(&creature); }
Generic 함수 줄여쓰기
다음과 같이 줄여쓸 수 있음
fn my_function(foo: impl Foo) { // 타입 앞에 'impl' 키워드 ... }
다음과 동일
fn my_function<T>(foo: T) where T:Foo { ... }
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } fn generic_make_noise(creature: &impl NoiseMaker) { // whwere -> impl // 컴파일 타임에 실제 자료형을 알게 됩니다 creature.make_noise(); } fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; generic_make_noise(&creature); }
Box
Box
는 stack에 있는 데이터를 heap으로 옮길 수 있게 해주는 데이터 구조
Box
는 smart pointer로도 알려진 struct이며 heap에 있는 데이터를 가리키는 포인터
Box
는 크기가 알려져 있는 struct (그저 포인터만 저장하고 있으므로)
- field의 크기를 알아야 하는 struct에 뭔가의 참조를 저장할 때 종종 사용
- 많이 씀
Box::new(Foo { ... })
struct SeaCreature { pub name: String, noise: String, } impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } } trait NoiseMaker { fn make_noise(&self); } impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } } struct Ocean { animals: Vec<Box<dyn NoiseMaker>>, } fn main() { let ferris = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; let sarah = SeaCreature { name: String::from("Sarah"), noise: String::from("swish"), }; let ocean = Ocean { animals: vec![Box::new(ferris), Box::new(sarah)], }; for a in ocean.animals.iter() { a.make_noise(); } }
Generic 구조체 다시 보기
- generic struct는 trait으로 제한된 매개변수 자료형 가질 수 있음
struct MyStruct<T> where T: MyTrait { foo: T ... }
- 매개변수 자료형은 generic structure의 구현 블록 안에 표시
impl<T> MyStruct<T> { ... }
The Rust Programming Language 링크
- 5.3 메서드 문법
- 10.2 트레이트로 공통된 동작을 정의하기
의문점
- generic 함수에서 where 절의 타겟 trait을 여러개 설정 가능한데, impl로 줄여서 표현은 어떻게?