[Rust] 러스트가 객체 지향적인 코드를 짜는 방법

devlcw·2024년 4월 8일
post-thumbnail

오늘은 러스트에서 어떻게 객체 지향적인 코드를 짜는지 알아보겠다.
러스트는 전통적인 객체 지향(OOP)은 아니다. 그렇지만 객체 지향적인 코드는 작성할 수 있다.

구조체

러스트는 구조체로 객체에서 사용할 필드를 정의할 수 있다.

struct Foo {
    name: String
}

구현 (Impl)

구조체로 필드를 정의했으면 이 객체의 고유 동작을 구현(impl)을 통해 정의할 수 있다.
상속이 없는 대신 이 구현 문법이 굉장히 많이 쓰인다.
한 구조체에 여러 개의 구현이 존재할 수 있다. 이런 역할이 상속을 대체하는 것이다.
앞서 트레잇을 구현할 때에도 이 문법이 쓰인다.

impl Foo {
    fn new() -> Self {
    	Foo {
            name: "Bar".into()
        }
    }
}

Self를 인자로 넘기는 방법

또, 메소드를 구현할 때 self를 참조하는 방법과 사용하는 방법이 있다.

fn bar(&self) {
    println!("{}", &self.name);
}

이렇게 self를 참조하게 될 경우 불변 참조가 만들어지며 self를 소비하지 않는다.

가변 참조로도 만들 수 있다.

fn bar1(&mut self) {
    println!("{}", &self.name);
}

self를 소비하지 않고 self 값을 변경할 수 있는 메소드이다.

마지막으로 소비하는 형태의 메소드를 만들 수 있는데,

fn bar2(self) {
    // do something
}

이렇게 만들면 소유권이 메소드 스코프로 move 되며 더 이상 밖에서 사용하는 것이 불가능하다.

let foo = Foo::new();
foo.bar1();
foo.bar1();
foo.bar2(); // moved to scope

// not available code
// foo.bar();

트레잇 (trait)

트레잇을 간단하게 설명을 하자면

타입이 가져야 할 공유 동작을 정의하는 인터페이스

이다.

이 트레잇은 한 타입에 여러 개가 구현될 수도 있다.
즉 OOP에서 인터페이스 역할을 대신 하는 것이다.

trait Foo {
    fn bar(&self) -> bool;
}

impl Foo for Bar {
    fn bar(&self) -> bool {
        true
    }
}

위와 같은 방식으로 객체 지향을 흉내낼 수 있다. 배운 것들을 정리해보면 다음과 같은 코드로 객체 지향을 흉내낼 수 있다. ResponseType은 임의의 enum 타입이라고 하자.

trait Response {
    fn response_type(&self) -> ResponseType;
}

struct BasicResponse {
    r#type: String,
}

impl Response for BasicResponse {
    fn new() -> Self {
    	BasicResponse {
        	r#type: "FOO".into()	
        }
    }

    fn response_type(&self) -> ResponseType {
		ResponseType::Basic
	}
}

fn main() {
	let response = BasicResponse::new();
    // do something
}

다음 글에서는 공유 동작에서 안전하지 않은 행동들, 즉 object safety에 대해서 알아보겠다.

profile
모든 스택에는 이유가 있다

0개의 댓글