본문 바로가기
IT/Rust 기초 완전 정복

Rust 구조체와 열거형 다루기 (8)

by 지식 발전소 2024. 4. 24.
728x90
반응형

1

 

구조체 (Struct)

친구들과 대화를 나누다 보면 구조체라는 용어가 종종 등장합니다. "프로그래밍을 하려면 구조체를 꼭 알아야 해." 또는 "이 프로젝트에서는 구조체를 많이 사용했어." 등의 말을 들었을 것입니다. 그렇다면 과연 구조체는 무엇일까요? 구조체가 왜 중요한지, 어떻게 사용하는지 자세히 알아보겠습니다.

구조체란?

구조체(Struct) 는 관련된 데이터를 하나의 단위로 묶어 표현하는 사용자 정의 데이터 타입입니다. 예를 들어 사람의 이름, 나이, 주소 등의 정보를 하나의 구조체로 정의할 수 있습니다.

Rust에서 구조체를 정의하는 방법은 다음과 같습니다.

struct Person {
    name: String,
    age: u32,
    address: String,
}

위 코드에서 Person은 구조체의 이름이며, name, age, address는 구조체의 필드(field)입니다. 각 필드에는 해당하는 데이터 타입이 지정되어 있습니다.

구조체 인스턴스 생성

정의된 구조체를 사용하려면 인스턴스를 생성해야 합니다. 이때 구조체 리터럴 문법을 사용합니다.

let person = Person {
    name: String::from("Alice"),
    age: 30,
    address: String::from("123 Main St."),
};

위 코드에서 person은 Person 구조체의 인스턴스입니다. 각 필드에 해당하는 값을 지정하여 인스턴스를 생성했습니다.

구조체 인스턴스를 생성할 때 모든 필드를 초기화하지 않아도 됩니다. 일부 필드만 지정하고 나머지는 기본값으로 초기화할 수 있습니다.

let person = Person {
    name: String::from("Alice"),
    ..Default::default() // 나머지 필드는 기본값으로 초기화
};

구조체 메서드

구조체에는 데이터뿐만 아니라 메서드도 정의할 수 있습니다. 메서드는 구조체와 관련된 동작을 수행하는 함수입니다.

impl Person {
    fn new(name: &str, age: u32, address: &str) -> Person {
        Person {
            name: String::from(name),
            age,
            address: String::from(address),
        }
    }

    fn greet(&self) {
        println!("Hello, my name is {} and I'm {} years old.", self.name, self.age);
    }
}

위 코드에서 new 메서드는 Person 구조체의 인스턴스를 생성하는 역할을 합니다. greet 메서드는 인스턴스의 name과 age 필드를 출력합니다.

메서드 내부에서 self를 통해 구조체 인스턴스에 접근할 수 있습니다. self는 구조체 인스턴스 자체를 가리키는 키워드입니다.

구조체의 메서드를 호출하는 방법은 다음과 같습니다.

let alice = Person::new("Alice", 30, "123 Main St.");
alice.greet(); // "Hello, my name is Alice and I'm 30 years old."

구조체는 관련 데이터와 동작을 하나의 단위로 묶어주므로, 코드의 구조화와 모듈화에 유용합니다. 또한 구조체는 객체지향 프로그래밍의 기반이 되므로 Rust 프로그래밍에서 매우 중요한 개념입니다.

구조체 기능

지금까지 구조체의 기본적인 정의와 사용법을 살펴보았습니다. 하지만 구조체에는 이보다 더 다양하고 유용한 기능이 있습니다. 이번에는 구조체의 고급 기능에 대해 알아보겠습니다.

구조체 업데이트 문법

Rust에서는 기존 구조체 인스턴스를 업데이트할 때 매우 간편한 문법을 제공합니다.

struct Person {
    name: String,
    age: u32,
    address: String,
}

let mut alice = Person {
    name: String::from("Alice"),
    age: 30,
    address: String::from("123 Main St."),
};

alice.age = 31; // 일반적인 업데이트 방식

alice = Person {
    age: 32,
    ..alice // 기존 필드 값을 유지하며 age 필드만 업데이트
};

위 코드에서 ..alice 문법을 사용하면 기존 alice 인스턴스의 필드 값을 유지한 채로 age 필드만 업데이트할 수 있습니다. 이는 코드의 가독성과 유지보수성을 높여줍니다.

튜플 구조체

Rust에서는 튜플 구조체 라는 특별한 형태의 구조체를 정의할 수 있습니다. 튜플 구조체는 구조체의 필드 이름을 지정하지 않고, 단순히 타입만 나열하여 정의합니다.

struct Color(u8, u8, u8);

let red = Color(255, 0, 0);

위 코드에서 Color는 튜플 구조체입니다. 필드 이름 대신 타입만 지정되어 있습니다. 인스턴스 생성 시에는 값을 직접 제공합니다.

튜플 구조체는 간단한 데이터 집합을 표현할 때 유용합니다. 또한 여러 개의 값을 함수에 전달하거나 반환할 때도 사용됩니다.

유닛 구조체

유닛 구조체는 필드가 전혀 없는 구조체입니다. 다음과 같이 정의할 수 있습니다.

struct Marker;

유닛 구조체는 그 자체로 의미를 가지며, 주로 특성을 나타내는 역할을 합니다. 예를 들어, 다양한 동작을 수행하는 트레이트에서 유닛 구조체를 사용하여 동작의 종류를 구분할 수 있습니다.

구조체 필드 가시성

Rust에서는 구조체의 필드에 대한 가시성을 제어할 수 있습니다. 기본적으로 구조체의 필드는 private으로 취급되지만, pub 키워드를 사용하여 public으로 설정할 수 있습니다.

struct Person {
    pub name: String, // public 필드
    age: u32, // private 필드
    address: String, // private 필드
}

위 코드에서 name 필드는 public이므로 외부에서 접근할 수 있지만, age와 address 필드는 private이므로 구조체 내부에서만 접근 가능합니다.

필드의 가시성을 제어하면 구조체의 내부 구현을 숨기고 공개 인터페이스만 노출할 수 있습니다. 이를 통해 코드의 캡슐화와 모듈성을 높일 수 있습니다.

구조체는 Rust 프로그래밍에서 매우 중요한 역할을 합니다. 관련 데이터와 동작을 하나의 단위로 묶어주며, 객체지향 프로그래밍의 기반이 됩니다. 이번 장에서는 구조체의 정의와 인스턴스 생성, 메서드 정의 및 호출 방법을 살펴보았습니다. 또한 구조체 업데이트 문법, 튜플 구조체, 유닛 구조체, 필드 가시성 등 구조체의 고급 기능도 다루었습니다. 이러한 내용을 잘 이해하고 활용한다면 Rust 프로그래밍 실력이 크게 향상될 것입니다.

열거형 (Enum)

프로그래밍에서 열거형(Enumeration, Enum) 은 서로 관련된 여러 가지 변종을 하나의 타입으로 정의할 수 있게 해주는 데이터 타입입니다. 열거형은 코드의 가독성과 안전성을 높여주므로 Rust 프로그래밍에서 매우 중요한 개념입니다. 이번에는 Rust에서 열거형을 어떻게 정의하고 사용하는지 자세히 알아보겠습니다.

열거형 정의

Rust에서 열거형은 enum 키워드를 사용하여 정의합니다. 가장 기본적인 형태는 다음과 같습니다.

enum Color {
    Red,
    Green,
    Blue,
}

위 코드에서 Color는 열거형의 이름이며, Red, Green, Blue는 열거형의 변종(variant)입니다. 각 변종은 서로 다른 값을 나타냅니다.

열거형의 변종에는 연관된 데이터를 저장할 수도 있습니다. 이를 열거형 데이터(Enum Data) 라고 합니다.

enum Message {
    Quit, // 단순한 변종
    Move { x: i32, y: i32 }, // 구조체 데이터를 가진 변종
    Write(String), // 단일 타입 데이터를 가진 변종
    ChangeColor(i32, i32, i32), // 튜플 데이터를 가진 변종
}

위 코드에서 Message 열거형의 각 변종은 서로 다른 형태의 데이터를 가지고 있습니다. Quit은 변종 자체만 존재하고, Move는 x, y 필드를 가진 구조체 데이터를, Write는 String 타입의 단일 데이터를, ChangeColor는 i32 타입의 튜플 데이터를 갖습니다.

열거형 사용

정의된 열거형은 변종으로 인스턴스를 생성하여 사용합니다.

let color = Color::Red;
let msg = Message::Write(String::from("Hello"));

위 코드에서 color는 Color::Red 변종으로 초기화되었고, msg는 Message::Write 변종에 "Hello" 문자열을 제공하여 초기화되었습니다.

열거형 변종에 저장된 데이터에 접근하려면 match 식을 사용합니다.

match msg {
    Message::Quit => println!("Quitting..."),
    Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
    Message::Write(text) => println!("Writing: {}", text),
    Message::ChangeColor(r, g, b) => println!("Color: ({}, {}, {})", r, g, b),
}

위 코드에서 match 식은 msg의 변종을 분기하여 각각 다른 동작을 수행합니다. 이렇게 열거형을 사용하면 유효한 값만 처리할 수 있으므로 프로그램의 안전성이 높아집니다.

열거형의 활용

열거형은 Rust 프로그래밍에서 다양한 곳에 활용됩니다.

  • 상태 머신(State Machine) 구현
  • 오류 처리
  • 옵션 값 표현 (Option 타입)
  • 결과 값 표현 (Result 타입)
  • 다형성 구현 (trait object)

예를 들어, HTTP 요청의 결과를 나타내는 RequestResult 열거형은 다음과 같이 정의할 수 있습니다.

enum RequestResult<T, E> {
    Ok(T),
    Err(E),
}

여기서 T는 성공 시 반환되는 값의 타입이고, E는 오류 유형입니다. RequestResult는 표준 라이브러리의 Result 타입과 유사한 역할을 합니다.

이처럼 열거형은 프로그램의 다양한 상황과 값을 표현하는 데 매우 유용합니다. 열거형을 잘 활용하면 코드의 안전성과 가독성을 크게 높일 수 있습니다.

구조체와 열거형 고급 기능

지금까지 구조체와 열거형의 기본적인 정의와 사용법을 살펴보았습니다. 하지만 Rust에서 구조체와 열거형은 이보다 더 다양하고 강력한 기능을 제공합니다. 이번에는 구조체와 열거형의 고급 기능에 대해 알아보겠습니다.

구조체 메서드

앞서 구조체에 메서드를 정의할 수 있다고 설명했습니다. 메서드는 구조체의 동작을 캡슐화하고 코드의 모듈성을 높여줍니다. 구조체 메서드에는 몇 가지 특별한 종류가 있습니다.

연관 함수 (Associated Functions)

연관 함수는 구조체의 인스턴스를 생성하거나 초기화하는 역할을 합니다. 이전에 살펴본 new 함수가 대표적인 연관 함수입니다.

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle {
            width,
            height,
        }
    }
}

연관 함수는 self를 매개변수로 받지 않으며, Rectangle::new()와 같은 형태로 호출합니다.

메서드 (Methods)

메서드는 구조체 인스턴스에 대한 동작을 정의합니다. 메서드 내부에서는 self를 통해 구조체 인스턴스에 접근할 수 있습니다.

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

let rect = Rectangle::new(3, 4);
println!("Area: {}", rect.area()); // 12

위 코드에서 area 메서드는 self를 불변 참조(&self)로 받아 width와 height를 사용하여 면적을 계산합니다.

메서드 내에서 self가 받는 값에 따라 메서드의 종류가 나뉩니다.

  • &self : 불변 참조, 인스턴스를 변경할 수 없음
  • &mut self : 가변 참조, 인스턴스를 변경할 수 있음
  • self : 값, 인스턴스를 소유하며 변경 가능

트레이트 메서드 (Trait Methods)

트레이트 메서드는 구조체가 구현하는 트레이트에 정의된 메서드입니다. 트레이트는 Rust의 인터페이스 역할을 하므로, 트레이트 메서드를 통해 구조체의 동작을 정의할 수 있습니다.

trait Shape {
    fn area(&self) -> u32;
}

impl Shape for Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

위 코드에서 Shape 트레이트는 area 메서드를 정의하고 있습니다. Rectangle 구조체는 이 트레이트를 구현하여 area 메서드를 제공합니다.

열거형 메서드

열거형 역시 메서드를 가질 수 있습니다. 열거형 메서드는 열거형의 변종에 따라 다른 동작을 수행할 수 있습니다.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quitting..."),
            Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
            Message::Write(text) => println!("Writing: {}", text),
            Message::ChangeColor(r, g, b) => println!("Color: ({}, {}, {})", r, g, b),
        }
    }
}

위 코드에서 Message 열거형은 call 메서드를 가지고 있습니다. call 메서드는 열거형 변종에 따라 다른 동작을 수행합니다.

패턴 매칭 (Pattern Matching)

Rust에서 매우 강력한 기능 중 하나가 패턴 매칭(Pattern Matching) 입니다. 패턴 매칭을 통해 복잡한 데이터 구조를 간편하게 분해할 수 있습니다.

앞서 match 식을 사용하여 열거형의 변종을 분기한 예시를 보았습니다. 하지만 패턴 매칭은 이보다 더 다양한 곳에서 사용할 수 있습니다.

구조체 패턴

구조체 인스턴스를 분해할 때 패턴 매칭을 사용할 수 있습니다.

struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 3, y: 4 };

match p {
    Point { x, y } => println!("Point at ({}, {})", x, y), // (3, 4)
}

위 코드에서 p 인스턴스는 x와 y 필드로 분해되었습니다.

또 다른 패턴

패턴 매칭은 리터럴 값, 구조체 필드, 튜플 요소 등 다양한 형태의 패턴을 지원합니다.

let x = 5;
let y = Some(10);

match (x, y) {
    (3, Some(7)) => println!("Not this one"), 
    (val, None) => println!("x is {}", val), // 실행되지 않음
    (5, Some(n)) => println!("x is 5, n is {}", n), // "x is 5, n is 10"
    _ => println!("Neither"), // 실행되지 않음
}

위 예시에서 _는 모든 값과 매칭되는 와일드카드 패턴입니다. val은 변수 바인딩 패턴으로, 값을 새로운 변수에 바인딩합니다.

패턴 매칭은 구조체와 열거형뿐만 아니라 다양한 곳에서 사용되며, 코드의 간결성과 안전성을 높여줍니다.

옵셔널 필드와 타입 구체화

구조체와 열거형에는 옵셔널 필드(Optional Fields)  타입 구체화(Type Aliases) 라는 기능도 있습니다.

옵셔널 필드

구조체에서 필드를 선택적으로 초기화할 수 있습니다. 이를 위해서는 필드에 Option 타입을 사용해야 합니다.

struct Person {
    name: String,
    age: u8,
    email: Option<String>,
}

let alice = Person {
    name: String::from("Alice"),
    age: 30,
    email: None, // 이메일 정보가 없음
};

let bob = Person {
    name: String::from("Bob"),
    age: 25,
    email: Some(String::from("bob@example.com")), // 이메일 정보 제공
};

위 코드에서 email 필드는 Option<String> 타입으로, 값이 존재할 수도 있고 없을 수도 있습니다.

타입 구체화

타입 구체화(Type Aliases) 는 기존 타입에 별칭을 붙이는 기능입니다. 이를 통해 코드의 가독성을 높일 수 있습니다.

type Address = String;

struct Person {
    name: String,
    age: u8,
    addr: Address,
}

let alice = Person {
    name: String::from("Alice"),
    age: 30,
    addr: String::from("123 Main St."),
};

위 코드에서 Address는 String 타입의 별칭입니다. Person 구조체의 addr 필드는 Address 타입을 사용하고 있습니다.

타입 구체화는 열거형에서도 사용할 수 있습니다.

type Message = u8;

enum Request {
    Get(Message),
    Post(Message, String),
}

이처럼 구조체와 열거형은 다양한 기능과 유연성을 제공합니다. 이번 장에서는 구조체와 열거형의 고급 기능인 메서드, 패턴 매칭, 옵셔널 필드, 타입 구체화 등에 대해 알아보았습니다. 이러한 기능을 잘 활용하면 더욱 안전하고 모듈화된 코드를 작성할 수 있습니다.

참고 자료

  1. 스티브 클래인버그, "The Rust Programming Language", No Starch Press, 2018.
  2. Rust 공식 문서, "Structs", "Enums", https://doc.rust-lang.org/book/
  3. Michael Sampson, "Rust in Action", Manning Publications, 2021.
  4. Jim Blandy, Jason Orendorff, Leonora Tindall, "Programming Rust: Fast, Safe Systems Development", O'Reilly Media, 2021.
  5. "The Rust Reference", Rust 언어 참조 문서, https://doc.rust-lang.org/reference/

 

 

한 고대 문서 이야기

여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오

gospel79.tistory.com

 

유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법

올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을

stock79.tistory.com

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

728x90
반응형

댓글