구조체의 정의

구조체 선언

먼저 파이썬에서 클래스를 하나 정의해 보겠습니다. Person 클래스는 객체화 시 name, age 두 변수를 파라미터로 받고, self.name, self.age 라는 인스턴스 프로퍼티에 할당됩니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

러스트에서 구조체를 선언하기 위해서는 struct 키워드 뒤에 구조체 이름을 명시하면 됩니다. 구조체 안에서는 필드명: 타입명 으로 필드를 적어줍니다. 필드를 통해 변수를 구조체에 묶어둘 수 있습니다. 여기서 #[derive(Debug)] 는 미리 정의되어 있는 기능으로(derived trait 라고 합니다), 구조체의 내용을 보기 위해서 필요합니다.

#[derive(Debug)] // derived traits
struct Person {
    name: String,
    age: i32,
}

이제 각각 인스턴스를 생성해 보겠습니다.

jane = Person("jane", 30)
jane.age += 1
print(jane.name, jane.age)
print(jane.__dict__)

그리고 프로퍼티를 변경하고 출력해 보겠습니다. 마지막으로 __dict__ 를 이용해 인스턴스를 딕셔너리로 출력해 볼 수도 있습니다.

#[derive(Debug)] // derived traits
struct Person {
    name: String,
    age: i32,
}

fn main() {
    let mut jane = Person {
        name: String::from("Jane"),
        age: 30
    };
    jane.age += 1;
    println!("{} {}", jane.name, jane.age);
    println!("{:?}", jane);
}

인스턴스를 생성할 때는 구조체 이름 뒤에서 {필드명: 값} 문법으로 값들을 넣어주면 됩니다. 프로퍼티는 파이썬과 동일하게 접근과 변경이 가능합니다. 그런데 여기서 인스턴스 jane 이 mutable로 선언되었습니다. 왜냐하면 jane.age 의 값을 변경하고 있기 때문에 구조체 자체가 mutable로 선언되어야 합니다.

마지막으로 jane을 출력하는데 "{:?}" 가 사용되었는데, 이 문법은 디버그 출력이라고 합니다. 원래 러스트에서 어떤 값을 출력하려면 그 값은 Format이 정의되어 있어야 하는데, Person 구조체는 정의되어 있지 않기 때문에 디버그 출력을 이용해 간편하게 내용을 확인할 수 있습니다.

메소드

만일 파이썬의 Person 클래스를 객체화할 때, alive = True 라는 프로퍼티를 추가하고 싶다면 아래와 같이 할 수 있습니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.alive = True

함수는 메소드를 통해 구조체에 묶을 수 있습니다. impl 키워드 뒤에 구조체 이름을 명시해서 해당 구조체에 속한 메소드를 선언할 수 있습니다. 파이썬의 생성자와 마찬가지로, 객체화할때 사용되는 new 라는 함수를 정의할 수 있습니다. 메소드가 아닌 "함수"도 구조체 정의에 포함시킬 수 있는데, 이 경우는 연관 함수(Associated function)이라고 부르고, 파라미터에 self가 들어있지 않습니다. 먼저 구조체 정의에 alive: bool', 을 추가합니다.

#[derive(Debug)] // derived traits
struct Person {
    name: String,
    age: i32,
    alive: bool,
}

impl Person {
    fn new(name: &str, age: i32) -> Self {
        Person {
            name: String::from(name),
            age: age,
            alive: true,
        }
    }
}

new 함수의 리턴 타입이 Self인데, 자신이 속한 구조체 타입인 Person 클래스를 리턴한다는 의미입니다. 물론 -> Person으로 써도 동일하지만, Self 가 더 권장되는 방법입니다. 그리고 이 함수 안에서 구조체를 alive: true 값을 넣어서 생성하고 있는 것을 알 수 있습니다.

인스턴스를 생성하는 메소드 말고 일반적인 메소드도 추가가 가능합니다. 먼저 파이썬에서는

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.alive = True
        
    def info(self):
      	print(self.name, self.age)

    def get_older(self, year):
        self.age += year

러스트에서는 아래와 같습니다.

impl Person {
    fn new(name: &str, age: i32) -> Person {
        Person {
            name: String::from(name),
            age: age,
            alive: true,
        }
    }

    fn info(&self) {
        println!("{} {}", self.name, self.age)
    }

    fn get_older(&mut self, year: i32) {
        // if we don't borrow the ownership, ownership will be moved to the
        // function and the variable will be dropped
        // self must be passed as mutable reference
        self.age += year;
    }
}

이때 self 가 borrowed 되면서 mutable 인 것에 주의합니다. 왜냐하면 인스턴스 프로퍼티가 변경되기 때문에 self가 mutable이어야 합니다. 여기서 info 메소드의 &selfself로 바꾸면 어떻게 될까요?

다시 인스턴스를 생성하고 메소드를 호출해 보겠습니다.

john = Person("john", 20)
john.info()
john.get_older(3)
john.info()

get_older 메소드를 통해 age가 3 증가합니다. 러스트에서도 동일합니다.

fn main() {
    let mut john = Person::new("john", 20);
    john.info();
    john.get_older(3);
    john.info();
}

정리하자면, 구조체 안에는 self 파라미터를 사용하지 않는 연관 함수와 self 파라미터를 사용하는 메소드 모두 정의될 수 있습니다.

튜플 구조체(Tuple struct)

마지막으로 튜플 구조체를 간단히 알아보겠습니다. 튜플 구조체는 구조체 필드가 이름 대신 튜플 순서대로 정의되는 구조체입니다. 필드 참조 역시 튜플의 원소를 인덱스로 참조하는 것과 동일합니다.

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

    println!("{} {}", black.0, origin.0);
}