문자열과 슬라이스로 데이터를 안전하게 빌릴 수 있게 되었으니, 이제 여러 값을 하나의 타입으로 묶을 차례입니다. Rust의 struct는 연관된 데이터를 의미 있게 이름 붙여 묶는 도구이고, [[impl|impl 블록]]은 그 데이터에 대한 행동을 정의합니다. 이번 글에서는 학습 진행도를 추적하는 작은 구조체를 만들고, 불변·가변 참조 규칙이 메서드에도 똑같이 적용된다는 것을 확인합니다.
구조체 정의하기
struct StudyLog {
topic: String,
completed: bool,
notes: Vec<String>,
}
fn main() {
let mut log = StudyLog {
topic: String::from("borrowing"),
completed: false,
notes: vec![String::from("불변 참조 복습")],
};
println!("{} 진행 중", log.topic);
log.notes.push(String::from("가변 참조 예제 작성"));
println!("노트 개수: {}", log.notes.len());
}
구조체 필드는 기본적으로 공개되지 않습니다. 이 예제는 같은 모듈 안에서만 사용하므로 그대로 접근할 수 있지만, 보통은 메서드를 통해 값을 읽고 수정하도록 설계합니다.
impl 블록과 메서드
메서드를 정의할 때는 첫 번째 인자로 self, &self, &mut self 중 하나를 선택합니다. 각 선택은 ownership과 borrowing 규칙을 그대로 반영합니다.
&self: 빌려서 읽기만 함&mut self: 빌려서 수정함- self: 값을 완전히 가져감
impl StudyLog {
fn new(topic: &str) -> Self {
Self {
topic: topic.to_string(),
completed: false,
notes: Vec::new(),
}
}
fn add_note(&mut self, memo: &str) {
self.notes.push(memo.to_string());
}
fn summary(&self) -> String {
format!("{} / 완료 여부: {} / 메모 {}건",
self.topic,
self.completed,
self.notes.len(),
)
}
fn finish(self) -> StudyLog {
StudyLog {
completed: true,
..self
}
}
}
fn main() {
let mut log = StudyLog::new("strings");
log.add_note("String vs &str 구분");
log.add_note("슬라이스 안전 범위");
println!("{}", log.summary());
let finished = log.finish();
// println!("{}", log.summary()); // 컴파일 오류: log는 finish로 이동됨
println!("완료 상태: {}", finished.summary());
}
new는 소유권을 가진StudyLog를 반환합니다.add_note는 가변 참조로 호출되므로, 동시에 다른 곳에서 불변 참조를 사용할 수 없습니다.summary는 불변 참조만 필요하므로&self를 사용합니다.finish는self를 값으로 받기 때문에 ownership을 가져오고, 메서드가 끝나면 기존 인스턴스를 더 이상 쓸 수 없습니다.
메서드 안에서 슬라이스와 참조 재사용하기
문자열과 슬라이스 개념을 구조체 메서드 안으로 옮겨오면 다음과 같이 동작합니다.
impl StudyLog {
fn latest_note(&self) -> Option<&str> {
self.notes.last().map(|note| note.as_str())
}
}
fn main() {
let mut log = StudyLog::new("structs");
log.add_note("필드 초기화 연습");
log.add_note("impl 블록 만들기");
if let Some(text) = log.latest_note() {
println!("최근 메모: {}", text);
}
}
Option<&str>는 구조체가 문자열을 소유한 채, 외부에는 슬라이스 참조만 빌려줍니다. 즉, 메모를 복사해서 새 문자열을 만드는 대신 "있으면 빌려주고, 없으면 None"으로 표현하는 방식입니다. 이 패턴은 소유권과 참조 규칙을 동시에 만족시켜, 불필요한 복사를 줄입니다.
손으로 따라하기
completed상태를 토글하는mark_done(&mut self)메서드를 구현하고, 불변 참조로 상태를 읽는 함수와 동시에 사용하면 어떤 경고가 나오는지 확인해 보세요.- 학습 시간을 분 단위로 저장하는
minutes필드를 추가하고,impl블록에서 평균 학습 시간을 계산하는 메서드를 작성해 보세요. - 구조체를 다른 함수에 넘길 때
self,&self,&mut self중 어떤 선택이 가장 안전하고 명확한지 직접 비교해 보세요.
마무리
구조체와 메서드는 Rust 코드에 맥락을 부여하는 기본 단위입니다. 필드마다 ownership을 명확히 선언하고, 메서드는 참조 규칙을 그대로 따르기 때문에 코드를 읽을 때 "어디서 데이터를 빌렸는가"가 한눈에 보입니다. 다음 글에서는 enum과 패턴 매칭으로 상태를 더 풍부하게 표현하며, Option과 같은 표준 enum을 구조체 안에서 어떻게 활용하는지 살펴보겠습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요