All posts

Rust 초급 6편: 구조체와 메서드로 학습 할 일 만들기

제목과 완료 여부를 각각 변수로 관리하면 어떤 값끼리 한 묶음인지 흐려집니다. 이번에는 학습 할 일 하나를 Task라는 타입으로 만듭니다. 제목과 상태를 구조체에 묶고, 완료 처리 동작을 메서드로 붙여 보겠습니다.

제목과 완료 표시가 있는 작업 카드를 만드는 게로 구조체와 메서드를 표현한 일러스트

제목과 완료 여부를 각각 변수로 관리하면 어떤 값끼리 한 묶음인지 흐려집니다. 이번에는 학습 할 일 하나를 Task라는 타입으로 만듭니다. 제목과 상태를 구조체에 묶고, 완료 처리 동작을 메서드로 붙여 보겠습니다.

준비와 목표

5편의 String과 참조를 사용합니다. 아래 명령으로 프로젝트를 만들고 핵심 실습 코드를 src/main.rs 전체에 넣습니다.

cargo new study_task
cd study_task

목표는 struct와 impl의 역할을 나누고, &self와 &mut self의 차이를 읽는 것입니다. 외부 크레이트는 사용하지 않습니다.

핵심 실습: 할 일 한 개 생성하고 완료하기

struct Task {
    title: String,
    completed: bool,
}

impl Task {
    fn new(title: String) -> Self {
        Self { title, completed: false }
    }

    fn label(&self) -> &str {
        if self.completed { "완료" } else { "진행 중" }
    }

    fn complete(&mut self) {
        self.completed = true;
    }
}

fn main() {
    let mut task = Task::new(String::from("구조체 배우기"));
    println!("{}: {}", task.title, task.label());
    task.complete();
    println!("{}: {}", task.title, task.label());
}
cargo run
구조체 배우기: 진행 중
구조체 배우기: 완료

struct Task는 데이터의 모양을 정의합니다. title과 completed는 필드입니다. main에서 만든 task는 그 모양에 실제 값을 채운 인스턴스입니다. 타입 이름과 값의 이름을 구분하세요. 구조체 공식 설명

impl 안에서 동작 정의하기

impl Task는 Task와 관련된 함수를 작성하는 영역입니다. new에는 self 인자가 없으므로 Task::new처럼 타입 이름으로 호출합니다. Rust가 자동으로 요구하는 생성자 문법이 아니라, 우리가 붙인 연관 함수 이름입니다.

Self는 이 impl 안에서 Task를 뜻합니다. Self { title, completed: false }의 title은 필드와 인자 이름이 같을 때 쓸 수 있는 축약형입니다. title: title과 같습니다.

new는 String을 받으므로 전달한 문자열의 소유권을 가져갑니다. 새 Task의 title 필드가 그 문자열을 소유하게 됩니다. 4편의 이동 규칙이 구조체를 만들 때도 적용된 셈입니다.

읽는 메서드와 수정하는 메서드

label(&self)는 Task를 읽기 전용으로 빌립니다. 호출 후에도 task를 사용할 수 있습니다. 반환한 &str은 고정된 상태 문구이며 새로운 String을 매번 만들지 않습니다.

complete(&mut self)는 Task를 가변으로 빌려 completed를 바꿉니다. 그래서 main의 task에도 mut를 붙였습니다. 메서드 호출에서 Rust가 필요한 참조를 자동으로 빌리므로 task.complete()라고 적습니다. 메서드 공식 설명

메서드의 self 형태를 읽으면 동작의 의도를 파악할 수 있습니다. &self는 읽기, &mut self는 변경 가능성, self는 인스턴스의 소유권을 가져가는 형태입니다. 이번 실습은 앞의 두 가지를 사용합니다.

출력과 Debug

println!("{}", task)는 지금 상태에서는 컴파일되지 않습니다. 우리가 만든 타입에 일반 표시 형식이 자동으로 생기는 것은 아니기 때문입니다. 위 코드는 필요한 필드를 직접 출력했습니다.

구조 전체를 진단용으로 보고 싶다면 struct Task 바로 위에 #[derive(Debug)]를 붙이고 println!("{task:?}");를 추가할 수 있습니다. Debug는 개발자가 상태를 확인하기 위한 출력 방식입니다. 사용자에게 보여줄 문구와 같은 목적으로 쓰이지 않을 수 있습니다.

오류를 고쳐 보기

let mut task에서 mut를 지운 뒤 cargo check를 실행하면 complete에서 가변으로 빌릴 수 없다는 오류가 납니다. label을 호출하는 것만으로는 mut가 필요하지 않습니다. 데이터를 만드는 것과 이후에 변경하는 것을 구분하세요.

구조체 리터럴로 Task를 직접 만들 때 completed를 빼먹으면 필드 누락 오류가 납니다. new 함수가 기본 상태를 한곳에서 정하도록 만든 이유입니다. 이후 생성 지점이 늘어도 매번 false를 적지 않아도 됩니다.

연습과 해설

완료된 작업을 다시 진행 중으로 바꾸는 reopen 메서드를 추가해 보세요. 완료 처리 후 reopen을 호출하고 상태를 출력하면 "진행 중"으로 돌아와야 합니다.

해설: impl Task 안에 fn reopen(&mut self) { self.completed = false; }를 추가합니다. main에서는 task.reopen(); 뒤에 기존 println!을 한 번 더 씁니다. 필드를 수정하므로 &self가 아닌 &mut self가 필요합니다.

두 번째 연습은 제목 변경입니다. fn rename(&mut self, title: String)을 만들고 self.title = title;을 실행합니다. 새 String이 필드로 이동하고 기존 제목은 정리됩니다. 소유권과 가변 참조를 함께 복습할 수 있습니다.

다음 편

bool로는 완료와 미완료만 표현했습니다. "대기", "진행 중", "완료"처럼 상태가 늘면 숫자나 문자열보다 열거형이 명확합니다. 7편에서는 enum과 match로 상태를 처리합니다.

실행 확인

이 글의 핵심 실습은 Rust 1.99.1, 2024 Edition의 Linux 환경에서 확인했습니다. 의도적으로 실패하는 예제는 설명한 오류가 발생하는지 구분해 검사했습니다. 운영체제별 오류 문구는 다를 수 있습니다.

Rust 초급 시리즈 전체 목차

  1. 설치와 Cargo로 첫 프로그램 만들기
  2. 변수·자료형·문자열로 학습 기록 만들기
  3. 함수·조건문·반복문으로 학습 기록 계산하기
  4. String으로 이해하는 소유권과 이동
  5. 참조와 빌림으로 문자열 읽고 수정하기
  6. 구조체와 메서드로 학습 할 일 만들기
  7. 열거형과 패턴 매칭으로 학습 상태 표현하기
  8. Option·Result로 빈 값과 오류 처리하기
  9. 벡터·해시맵·문자열로 학습 기록 모으기
  10. 모듈과 외부 크레이트로 코드 정리하기
  11. 테스트와 디버깅으로 목표 계산 검증하기
  12. 파일을 검색하는 CLI 프로젝트 완성하기

시리즈 모아보기

Discussion

No comments yet