Rust의 구조체, 메서드, 트레이트 및 제네릭 이해

구조체와 열거형

Rust에서 struct는 데이터 필드를 그룹화하는 데 사용되며, 단순한 데이터 컨테이너 역할을 합니다. 반면 enum은 특정 값들의 집합을 정의하는 데 적합합니다.

struct Person {
    full_name: String,
    sex: Gender,
}

enum Gender {
    Unknown = 0,
    Female = 1,
    Male = 2,
}

연관 함수와 메서드

impl 블록 내부에서 정의되는 함수는 두 가지 유형으로 나뉩니다. 첫 번째는 연관 함수로, 타입 자체에 바인딩된 함수입니다. 호출 시 타입::함수명() 형식을 사용합니다.

impl Person {
    fn create() -> Self {
        Person {
            full_name: "홍길동".into(),
            sex: Gender::Male,
        }
    }

    fn calculate_age(_a: i32, _b: i32) -> i32 {
        _a + _b
    }
}

두 번째는 메서드로, 첫 번째 인자가 &self 또는 &mut self 형태인 함수입니다. 이들은 인스턴스를 통해 호출되며, 문법적으로 인스턴스.메서드() 형태로 사용됩니다.

impl Person {
    fn rest(&self) {
        println!("잠자기 시작");
    }

    fn eat(&mut self, food: &str) {
        println!("먹는 중: {}", food);
    }
}

fn main() {
    let mut person = Person::create();
    person.rest();
    person.eat("김치찌개");
}

실제로 person.rest()는 내부적으로 Person::rest(&person)로 변환됩니다. 즉, 메서드는 연관 함수의 특수한 형태이며, 문법적 설탕일 뿐입니다. 마찬가지로 &selfself: &Person의 약어이고, &mut selfself: &mut Person을 의미합니다.

그러나 Person::rest(&person)처럼 직접 호출하는 경우는, 특정 트레이트의 동일한 이름의 메서드를 명시적으로 호출해야 할 때 필요합니다.

트레이트(Tratis): 인터페이스 정의

트레이트는 다른 언어에서의 인터페이스나 추상 클래스와 유사하며, 일정한 행동을 보장하기 위한 계약입니다. 이를 통해 다형성과 코드 재사용을 가능하게 합니다.

trait Sound {
    fn make_sound(&self) -> &str;
}

struct Sheep;
struct Cow;
struct Human;

impl Sound for Sheep {
    fn make_sound(&self) -> &str {
        "메리메리"
    }
}

impl Sound for Cow {
    fn make_sound(&self) -> &str {
        "음머머"
    }
}

impl Sound for Human {
    fn make_sound(&self) -> &str {
        "안녕하세요!"
    }
}

// 메서드 오버라이드 예시
impl Human {
    fn make_sound(&self) -> &str {
        "하하하"
    }
}

fn main() {
    let sheep = Sheep;
    let cow = Cow;
    let human = Human;

    println!("{}", sheep.make_sound());
    println!("{}", cow.make_sound());
    println!("{}", human.make_sound());

    // 인스턴스 메서드 호출
    println!("{}", Human::make_sound(&human));

    // 명시적인 트레이트 메서드 호출
    println!("{}", Sound::make_sound(&human));
}

트레이트를 파라미터로 사용

함수 매개변수로 트레이트를 지정하면, 해당 트레이트를 구현한 모든 타입의 인스턴스를 전달할 수 있습니다.

fn trigger_sound(animal: &impl Sound) {
    println!("{}", animal.make_sound());
}

여러 트레이트를 동시에 요구할 경우, + 기호로 연결합니다.

fn process(item: &T) { ... }

트레이트를 반환값으로 사용

함수의 반환값으로 트레이트를 지정할 수도 있습니다. 이는 구현을 숨기고, 유연한 타입 반환을 가능하게 합니다.

fn get_animal() -> impl Sound {
    Human
}

제네릭 (Generics)

제네릭은 타입을 일반화하여 다양한 타입에 대해 동일한 로직을 적용할 수 있게 해줍니다.

struct Point<T> {
    x: T,
    y: T,
}

fn type_of<T>(_value: &T) -> &'static str {
    std::any::type_name::<T>()
}

fn main() {
    println!("{}", type_of(&42));
    println!("{}", type_of(&"Hello".to_string()));
    println!("{}", type_of(&Point { x: 1.0, y: 2.0 }));
}

트레이트와 제네릭의 결합

트레이트 제약 조건을 제네릭과 함께 사용하면, 특정 행동을 보장하는 타입만 허용할 수 있습니다.

fn call_speak(item: &T) {
    println!("{}", item.make_sound());
}

또는 where 키워드를 사용해 더 복잡한 제약 조건을 표현할 수 있습니다.

fn call_speak<T>(item: &T)
where
    T: Sound,
{
    println!("{}", item.make_sound());
}

여러 트레이트를 동시에 요구할 때도 +를 사용합니다.

struct Container {
    data: T,
}

fn process_item(input: T) { ... }

어트리뷰트 (Attributes)

Rust는 컴파일러에게 추가 정보를 제공하는 어트리뷰트를 지원합니다. 예를 들어, 함수의 동작 방식이나 문서화, 테스트 설정 등을 제어할 수 있습니다.

공식 문서 참조: Rust Reference - Attributes

태그: struct Enum trait Method associated function

7월 2일 20:44에 게시됨