Rust 구조체
구조체(struct)는 서로 관련된 값들을 이름 붙은 하나의 타입으로 묶어요. 튜플이 이름 없이 빠르게 묶는 꾸러미라면, 구조체는 필드 하나하나에 이름을 붙여 줘요. 덕분에 구조체를 쓰는 코드가 훨씬 읽기 쉬워져요.
구조체 정의하고 만들기
섹션 제목: “구조체 정의하고 만들기”struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{}님은 {}살이에요", alice.name, alice.age);}실행 결과:
Alice님은 30살이에요튜플 ("Alice", 30)과 비교해 보세요. 구조체에서는 alice.name과 alice.age만 봐도 무슨 뜻인지 바로 알 수 있어요. 0번 필드가 이름이고 1번 필드가 나이라는 걸 외울 필요가 없죠.
구조체를 가변으로 만들기
섹션 제목: “구조체를 가변으로 만들기”변수와 마찬가지로 구조체 인스턴스도 기본적으로 불변이에요. 만든 뒤에 필드를 바꾸고 싶다면 mut를 붙이세요.
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{}님은 이제 {}살이에요", alice.name, alice.age);}메서드로 동작 추가하기
섹션 제목: “메서드로 동작 추가하기”impl 블록을 쓰면 구조체에 함수를 붙일 수 있어요. 그 안에서 &self는 메서드를 호출한 바로 그 인스턴스를 가리켜요.
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("안녕하세요, 저는 {}, {}살이에요", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}실행 결과:
안녕하세요, 저는 Alice, 30살이에요생성자 스타일의 함수
섹션 제목: “생성자 스타일의 함수”impl 안에 인스턴스를 만들어 주는 new 함수를 두는 패턴을 자주 써요. 그러면 호출하는 쪽에서 필드를 일일이 다 적지 않아도 돼요.
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{}님은 {}살이에요", bob.name, bob.age);}Person { name: name.to_string(), age } 안에 age가 혼자 쓰인 걸 보세요. 변수 이름이 필드 이름과 똑같을 때는 age: age라고 쓰지 않고 그냥 age라고만 써도 돼요. Rust에서는 이걸 필드 초기화 축약법(field init shorthand)이라고 불러요.
derive로 구조체 출력하기
섹션 제목: “derive로 구조체 출력하기”위에서 만든 구조체에 println!("{:?}", alice)를 써 보면 컴파일되지 않아요. 직접 만든 타입을 디버그 출력하는 방법을 Rust에게 알려 주지 않았기 때문이에요. 구조체 위에 #[derive(Debug)]를 붙이면 그 기능을 공짜로 얻을 수 있어요.
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}실행 결과:
Person { name: "Alice", age: 30 }#[derive(...)]는 타입에 흔히 쓰는 기능을 자동으로 만들어 주는 Rust의 방법이에요. Debug를 비롯해, 나중에 배울 Clone 같은 것들도 이렇게 추가하는 모습을 정말 자주 보게 될 거예요.
연습 문제
섹션 제목: “연습 문제”1. 직사각형 메서드
섹션 제목: “1. 직사각형 메서드”width와 height 필드(u32)를 가진 Rectangle 구조체를 만들어 보세요. 넓이를 돌려주는 area와, 두 변의 길이가 같으면 true를 돌려주는 is_square, 두 메서드를 추가하세요. 힌트: 두 메서드 모두 필드를 읽기만 하니까 &self를 받으면 돼요.
정답 보기
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("wide: 넓이 {}, 정사각형? {}", wide.area(), wide.is_square()); println!("square: 넓이 {}, 정사각형? {}", square.area(), square.is_square());}실행 결과:
wide: 넓이 24, 정사각형? falsesquare: 넓이 25, 정사각형? true2. 은행 계좌
섹션 제목: “2. 은행 계좌”owner(String)와 센트 단위의 balance(u32)를 가진 Account 구조체를 만들어 보세요. 잔액을 0으로 시작하는 Account::new("Ana")를 작성하세요. 그다음 필드를 바꿔서 50센트와 25센트를 입금하고, 계좌를 두 가지 방법으로 출력해 보세요. 평범한 문장으로 한 번, {:?}로 한 번이요. 힌트: account.balance를 바꾸려면 변수가 let mut여야 하고, {:?}를 쓰려면 #[derive(Debug)]가 필요해요.
정답 보기
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{}님의 잔액은 {}센트예요", account.owner, account.balance); println!("{:?}", account);}실행 결과:
Ana님의 잔액은 75센트예요Account { owner: "Ana", balance: 75 }new가 직접 balance: 0을 설정하기 때문에, 호출하는 쪽에서 잊어버릴 일이 없어요. 생성자 함수를 이렇게 많이 쓰는 큰 이유 중 하나예요.
3. 사람 목록
섹션 제목: “3. 사람 목록”Person 값 세 개를 벡터에 넣어 보세요. Ana(34살), Ben(16살), Chen(52살)이에요. 18살 이상인 사람의 이름을 모두 출력하고, 그다음 가장 나이가 많은 사람을 출력하세요. 힌트: for person in &people로 반복하고, let mut oldest = &people[0];로 지금까지 가장 나이가 많은 사람을 기억해 두세요.
정답 보기
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{}님은 성인이에요", person.name); } if person.age > oldest.age { oldest = person; } }
println!("최연장자: {} ({}살)", oldest.name, oldest.age);}실행 결과:
Ana님은 성인이에요Chen님은 성인이에요최연장자: Chen (52살)oldest는 복사본이 아니라 벡터 안을 가리키는 참조자(&Person)라서, 아무것도 복제되지 않아요. 더 나이 많은 사람을 찾을 때마다 oldest가 그 사람을 가리키도록 바꾸기만 하면 돼요.
- 구조체는 이름 붙은 필드들을 하나의 타입으로 묶어요:
struct Person { name: String, age: u32 }. - 인스턴스는
Person { name: ..., age: ... }로 만들어요. 나중에 필드를 바꾸려면mut를 붙이세요. impl블록으로 동작을 추가해요.&self는 메서드를 호출한 인스턴스를 가리켜요.#[derive(Debug)]를 붙이면 구조체를{:?}로 출력할 수 있어요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 튜플과 비교했을 때 구조체의 가장 큰 장점은 무엇인가요?
2. 구조체에 #[derive(Debug)]를 붙이면 무엇을 할 수 있나요?
3. Person { name: name.to_string(), age }에서 혼자 쓰인 age는 무슨 역할을 하나요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- 구조체 정의 및 인스턴트화 — The Rust Programming Language 한국어판: 구조체 갱신 문법(
..other), 튜플 구조체, 유사 유닛 구조체도 다뤄요. - 구조체를 사용한 예제 프로그램 — The Rust Programming Language 한국어판: 직사각형 프로그램을 따로 노는 변수에서 튜플로, 다시 구조체로 리팩터링하면서 구조체가 왜 도움이 되는지 보여 줘요.
- 메서드 문법 — The Rust Programming Language 한국어판:
&self와&mut self의 차이, 매개변수가 더 있는 메서드,new같은 연관 함수를 설명해요. - Structures — Rust by Example (영어): 모든 종류의 구조체를 실행 가능한 예제로 보여 주고, 마지막에 작은 연습 문제도 있어요.
- Derive — Rust by Example (영어):
Debug,Clone,PartialEq처럼#[derive(...)]로 만들 수 있는 것들을 알려 줘요.