구조체의 정의
구조체 선언
먼저 파이썬에서 클래스를 하나 정의해 보겠습니다. 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
메소드의 &self
를 self
로 바꾸면 어떻게 될까요?
다시 인스턴스를 생성하고 메소드를 호출해 보겠습니다.
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); }