Rust에서는 어떠한 방법으로도 데이터와 동작의 상속이 불가능.
구조체는 부모 구조체로부터 필드/함수를 상속 받을 수 없다.
Rust는 impl을 사용하여 추상화를 한다.
Rust는 struct와 함수를 연결할 수 있다. impl을 사용하면 struct와 연결 할 수 있는 함수를 정의할 수 있다.
C 언어에서 C++ 처럼 객체지향 언어로 사용하고자 할때 구조체 멤버에 함수 포인터를 두고 호출 하는 경우가 있는데 이 모습과 비슷하다.
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 키워드는 구조체의 필드와 메소드를 모듈 밖으로 노출시킨다.
Rust는 트레이트로 추상화/다형성을 지원
트레이트는 메소드의 집합을 구조체 데이터 타입에 연결할 수 있게 해준다.
이는 타 언어의 추상 클래스와 같은 역할을 수행한다.
// 트레이트
trait Animal {
// 시그니처만 선언하여 실제 구현은 구조체에서 구현 가능
// 단 반드시 구조체에서 구현 해야 컴파일 통과 가능
fn make_noise(&self);
// 구현을 trait 안에서 사용 가능
// 구조체에서 구현할 필요 없음
fn make_3_noise(&self) {
self.make_noise();
self.make_noise();
self.make_noise();
}
}
// 트레이트는 다른 트레이트로 부터 상속 받을 수 있다.
trait SuperAnimal : Animal {
fn make_alot_of_noise(&self) {
// 부모 트레이트로 부터 동작을 가져 올 수 있음
self.make_3_noise();
self.make_3_noise();
self.make_3_noise();
}
}
struct Dog {
pub name: String,
noise: String,
}
// 구조체에 메서드 연결
impl Dog {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
// 구조체에 트레이트 연결
impl Animal for Dog {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
impl SuperAnimal for Dog {
}
// 다른 구조체에도 적용. 다형성 실현
struct Cat {
pub name: String,
age: u32,
}
impl Animal for Cat {
fn make_noise(&self) {
println!("야옹 {}", self.age);
}
}
fn main() {
let dog = Dog {
name: String::from("Ferris"),
noise: String::from("wall~!"),
};
let cat = Cat {
name: String::from("Navi"),
age: 10,
};
dog.make_alot_of_noise();
cat.make_noise();
}
트레이트를 구현 코드를 보면 마치 구조체 처럼 impl을 앞에 사용 하였다.
impl Animal for Dog ...
이는 트레이트 역시 struct이기 때문이다.
메소드는 다음의 두 가지 방식으로 실행된다.
인스턴스의 데이터 타입을 알고 있는 경우, 어떤 함수를 호출해야 하는지 정확히 알고 있다.
인스턴스의 데이터 타입을 모르는 경우, 올바른 함수를 호출할 방법을 찾아야 한다.
트레이트의 자료형인 &dyn MyTrait은 동적 디스패치를 통해 객체의 인스턴스들을 간접적으로 작동시킬 수 있게 해준다.
Rust에서는 동적 디스패치를 사용할 경우 사람들이 알 수 있도록 트레이트 자료형 앞에 dyn을 붙일 것을 권고한다.
동적 디스패치는 실제 함수 호출을 위한 포인터 추적으로 인해 조금 느릴 수 있다.
fn static_make_noise(dog: &Dog) {
println!("dog sound is {}", dog.get_sound());
}
fn dynamic_make_noise(animal: &dyn Animal) {
animal.make_noise();
}
객체의 인스턴스를 &dyn MyTrait 데이터 타입을 가진 매개 변수로 넘길 떄, 이를 트레이트 객체라고 한다.
트레이트 객체는 인스턴스의 올바른 메소드를 간접적으로 호출할 수 있게 해주며, 인스턴스에 대한 포인터와 인스턴스 메소드들에 대한 함수 포인터 목록을 갖는 struct이다.
트레이트는 원본 구조체를 알기 어렵게 하느라 원래 크기 또한 알기 어렵다.
Rust에서 크기를 알 수 없는 값이 구조체에 저장될 때는 두가지 방법으로 처리
fn generice_make_noise<T>(item: &T)
where T: Animal {
item.make_3_noise();
}
// 트레이트로 제한한 제네릭은 줄여쓸 수 있다.
fn generice_make_noise(item: &impl Animal) {
item.make_3_noise();
}
Box는 스택에 있는 데이터를 힙으로 옮길 수 있게 해주는 자료 구조이다.
스마트 포인터로도 알려진 구조체이며 힙에 잇는 데이터를 가리키는 포인터를 들고 있다.
struct Wild {
animals: Vec<Box<dyn Animal>>,
}
fn main() {
let dog = Dog {
name: String::from("Ferris"),
noise: String::from("wall~!"),
};
let cat = Cat {
name: String::from("Navi"),
age: 10,
};
let wild = Wild {
animals: vec![Box::new(dog), Box::new(cat)],
};
for a in wild.animals.iter() {
a.make_noise();
}
}