DaleSchool

나만의 타입 만들기: 구조체

중급20분

학습 목표

  • struct로 사용자 정의 타입을 만들 수 있다
  • 구조체의 필드에 접근하고 값을 변경할 수 있다
  • impl 블록으로 구조체에 메서드를 추가할 수 있다
  • &self와 &mut self의 차이를 이해한다

동작하는 코드

사용자 정보를 저장한다고 생각해보세요. 이름, 나이, 이메일이 필요해요. 지금까지 배운 방법으로는 변수 세 개를 따로 만들어야 했어요. 구조체(struct) 를 쓰면 하나로 묶을 수 있어요!

#[derive(Debug)]
struct User {
    name: String,
    age: u32,
    email: String,
}

fn main() {
    let user = User {
        name: String::from("민수"),
        age: 25,
        email: String::from("minsu@example.com"),
    };

    println!("{:?}", user);
    println!("이름: {}", user.name);
}

#[derive(Debug)]를 붙이면 {:?}로 구조체 전체를 출력할 수 있어요. 아직 자세한 의미는 몰라도 돼요 — 지금은 "디버깅용 출력을 가능하게 해주는 마법"이라고 생각하세요.

직접 수정하기

  1. Useris_active: bool 필드를 추가하고, 값을 true로 설정해보세요.
  2. user.name을 수정하려면 어떻게 해야 할까요? user.name = String::from("지수");를 시도해보세요. 어떤 에러가 나나요?

"왜?" — 구조체와 소유권

구조체는 소유권의 연장이에요. UserString 필드를 가지면, 그 구조체가 문자열의 주인이에요. 구조체가 사라지면 필드의 값도 함께 정리돼요.

필드 수정하기

구조체의 값을 바꾸려면 변수 자체가 mut이어야 해요. Rust는 일부 필드만 가변으로 만드는 걸 허용하지 않아요 — 전체가 가변이거나 전체가 불변이에요.

#[derive(Debug)]
struct User {
    name: String,
    age: u32,
}

fn main() {
    let mut user = User {
        name: String::from("민수"),
        age: 25,
    };

    user.age = 26;  // mut이니까 수정 가능!
    println!("{:?}", user);
}

메서드 추가하기 — impl 블록

구조체에 동작을 추가하고 싶으면 impl 블록을 사용해요.

#[derive(Debug)]
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    // 메서드: &self로 자기 자신을 빌림
    fn area(&self) -> f64 {
        self.width * self.height
    }

    fn is_square(&self) -> bool {
        self.width == self.height
    }

    // 연관 함수: self가 없음 (생성자 패턴)
    fn square(size: f64) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect = Rectangle { width: 10.0, height: 5.0 };
    println!("넓이: {}", rect.area());
    println!("정사각형? {}", rect.is_square());

    let sq = Rectangle::square(7.0);
    println!("정사각형 넓이: {}", sq.area());
}

&self, &mut self, self — 빌림 규칙의 연장

메서드의 첫 번째 매개변수가 self의 형태를 결정해요. 이전 모듈에서 배운 빌림 규칙 그대로예요!

| 매개변수 | 의미 | 비유 | | ----------- | --------------------- | ------------------ | | &self | 읽기만 함 (불변 빌림) | 도서관에서 책 읽기 | | &mut self | 수정도 함 (가변 빌림) | 편집 권한으로 수정 | | self | 소유권을 가져감 | 가져가서 안 돌려줌 |

#[derive(Debug)]
struct Counter {
    count: i32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }

    fn value(&self) -> i32 {
        self.count          // 읽기만
    }

    fn increment(&mut self) {
        self.count += 1;    // 수정
    }
}

fn main() {
    let mut c = Counter::new();
    c.increment();
    c.increment();
    println!("카운트: {}", c.value());
}

increment는 값을 바꾸니까 &mut self, value는 읽기만 하니까 &self를 사용해요.

더 알아보기: 구조체에 &str을 넣으면?

구조체 필드에 &str을 넣으려고 하면 이런 에러가 나요.

struct User {
    name: &str,  // 에러!
}
error[E0106]: missing lifetime specifier

빌린 값(&str)을 구조체에 넣으려면 수명(lifetime) 을 지정해야 해요. "이 빌린 값이 얼마나 오래 유효한지" 컴파일러에게 알려줘야 하거든요.

수명은 모듈 15에서 배울 거예요. 지금은 구조체 필드에는 String을 쓰는 게 안전하다고 기억해두세요!

연습 1. Book 구조체를 만들고 summary 메서드를 추가해보세요.

// TODO: Book 구조체 정의 (title: String, author: String, pages: u32)
// TODO: #[derive(Debug)] 추가
// TODO: impl 블록에 summary 메서드 추가
//       "《제목》 - 저자 (N쪽)" 형태의 String을 반환

fn main() {
    let book = Book {
        title: String::from("Rust 프로그래밍"),
        author: String::from("Dale"),
        pages: 300,
    };
    println!("{}", book.summary());
    // 출력: 《Rust 프로그래밍》 - Dale (300쪽)
}

힌트: format! 매크로를 사용하면 편해요.

연습 2. Counter 구조체에 reset 메서드를 추가해보세요. count를 0으로 되돌려야 해요.

impl Counter {
    // ... 기존 메서드 ...

    fn reset(&mut self) {
        // 여기를 완성하세요
    }
}

힌트: 값을 수정하니까 어떤 종류의 self가 필요할까요?

Q1. 구조체의 필드를 수정하려면?

  • A) 필드 이름 앞에 mut을 붙인다
  • B) 변수를 let mut으로 선언한다
  • C) #[derive(Mutable)]을 추가한다
  • D) 필드 타입 앞에 mut을 붙인다

Q2. impl 블록에서 &self의 의미는?

  • A) 구조체의 소유권을 가져간다
  • B) 구조체를 불변으로 빌린다 (읽기만 가능)
  • C) 구조체를 가변으로 빌린다 (수정 가능)
  • D) 새로운 구조체를 만든다

Q3. Rectangle::square(5.0)처럼 self 없이 호출하는 함수를 뭐라고 하나요?

  • A) 메서드
  • B) 연관 함수
  • C) 클로저
  • D) 매크로