Rust OOP

mohadang·2023년 1월 22일
0

Rust

목록 보기
13/30
post-thumbnail

OOP 조건

  • 추상화, 캡슐화, 상속, 다형성

Rust는 OOP가 아니다(상속X)

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이기 때문이다.

  • 추상 클래스도 클래스이다.

동적 vs 정적 디스패치

메소드는 다음의 두 가지 방식으로 실행된다.

정적 디스패치(Static Dispatch)

인스턴스의 데이터 타입을 알고 있는 경우, 어떤 함수를 호출해야 하는지 정확히 알고 있다.

동적 디스패치(Dynamic Dispatch)

인스턴스의 데이터 타입을 모르는 경우, 올바른 함수를 호출할 방법을 찾아야 한다.

트레이트의 자료형인 &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이다.

  • 이런 함수 목록을 C++에서는 vtable이라고 한다.

크기를 알 수 없는 데이터

트레이트는 원본 구조체를 알기 어렵게 하느라 원래 크기 또한 알기 어렵다.
Rust에서 크기를 알 수 없는 값이 구조체에 저장될 때는 두가지 방법으로 처리

  • generics : 지정한 데이터 타입 및 크기의 구조체/함수를 생성
  • indirection : 인스턴스를 힙에 올림으로써 실제 데이터 타입의 크기 걱정 없이 그 포인터만 저장할 수 있는 간접적인 방법

제네릭 함수

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

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();
    }
}
profile
mohadang

0개의 댓글