앞선 글들에서 우리는 러스트의 기본 문법, 소유권 및 빌림 규칙, 컬렉션과 이터레이터를 다루며 러스트의 전반적인 프로그래밍 감각을 익혀왔습니다. 이제 조금 더 러스트다운 코드를 작성하기 위해 러스트에서의 구조체(Struct), 열거형(Enum), 그리고 C++와는 다른 문맥에서 강력한 기능을 제공하는 패턴 매칭(Pattern Matching), 마지막으로 모듈(Module)과 크레이트(Crate)를 통한 프로젝트 구조화 방법을 살펴보겠습니다.
C++에 익숙하다면 클래스, enum, 네임스페이스(namespace), 헤더/소스 파일 구조와 비교하며 러스트에서는 어떤 식으로 코드 조직과 타입 정의를 하는지 감을 잡을 수 있을 것입니다.
구조체(Struct) 정의하기
C++에서 class나 struct를 사용해 데이터를 캡슐화하듯이, 러스트에서는 struct로 타입을 정의합니다. 다만 러스트의 struct는 필드만 가지며, 메서드는 impl 블록에서 따로 정의합니다. 상속 개념은 없고, 필요한 공통 기능은 trait 구현을 통해 이뤄집니다.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("Point: ({}, {})", p.x, p.y);
}
C++의 struct와 달리 러스트에서는 접근 지정자(public/private)를 명시하지 않으면 기본적으로 필드는 비공개(private)입니다. 외부 모듈에서 접근하려면 pub 키워드로 필드를 공개해야 합니다.
C++ 클래스와 비교하자면, 러스트에서는 클래스 내부에 메서드를 직접 정의하지 않고 impl 블록 안에 따로 작성합니다.
impl Point {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
fn move_x(&mut self, delta: i32) {
self.x += delta;
}
}
fn main() {
let mut p = Point::new(0, 0);
p.move_x(5);
println!("Point: ({}, {})", p.x, p.y);
}
여기서 Self는 해당 구조체 타입을 가리키며, &mut self는 가변 참조를 나타냅니다. C++의 this 포인터(this->)와 유사하지만, 더 명확한 참조 개념을 가지고 있습니다.
튜플 구조체와 유닛 구조체
러스트에서는 필드 이름 없이 순서로만 필드를 구분하는 튜플 구조체가 있습니다. C++에는 유사한 개념이 거의 없지만, 복잡한 타입 명시 없이 단순히 이름을 붙여주는 용도로 사용할 수 있습니다.
struct Color(i32, i32, i32);
fn main() {
let red = Color(255, 0, 0);
println!("Red: ({}, {}, {})", red.0, red.1, red.2);
}
필드에 이름이 필요 없을 때 유용하며, 이 외에도 필드를 가지지 않는 유닛 구조체(empty struct)도 존재할 수 있습니다. 이는 타입 체계를 강화하거나 특정 trait 구현을 위한 마커로 활용될 수 있습니다.
열거형(Enum) 정의하기
C++에서 enum은 단순히 정수 상수 집합을 의미하는 경우가 많고, C++11 이후 enum class로 스코프를 부여할 수 있지만 여전히 데이터가 없는 빈 껍질에 가깝습니다. 반면 러스트의 enum은 각 variant마다 다른 종류의 데이터를 담을 수 있는 알찬 열거형입니다. 이는 C++의 std::variant나 std::optional에 가까운 개념입니다.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m = Message::Move { x: 10, y: 20 };
match m {
Message::Quit => println!("프로그램 종료"),
Message::Move { x, y } => println!("이동: x={}, y={}", x, y),
Message::Write(text) => println!("메시지: {}", text),
Message::ChangeColor(r, g, b) => println!("색상 변경: ({}, {}, {})", r, g, b),
}
}
여기서 열거형 Message는 각기 다른 타입의 데이터를 가질 수 있는 variant들을 갖습니다. C++ union이나 std::variant를 사용할 때처럼 별도의 타입 안전성을 얻기 위한 장치가 필요하지 않습니다. match 구문과 함께 사용하면 타입 안전하고 깔끔한 분기 처리가 가능합니다.
패턴 매칭(Pattern Matching)
러스트의 match 구문은 C++의 switch 구문보다 훨씬 강력하고 표현력 있습니다. 단순한 값 비교뿐 아니라, 구조체나 열거형, 튜플, 슬라이스 형태의 복합적인 패턴까지 매칭할 수 있습니다.
fn main() {
let numbers = (0, 5);
match numbers {
(0, y) => println!("첫 번째는 0이고, 두 번째는 {}", y),
(x, 0) => println!("두 번째는 0이고, 첫 번째는 {}", x),
(x, y) => println!("어떤 경우: x={}, y={}", x, y),
}
}
C++17부터 구조적 바인딩(structured binding)과 std::variant의 std::visit 등을 활용해 비슷한 스타일을 구현할 수는 있지만, 러스트의 match는 언어 차원에서 이 모든 패턴을 자연스럽게 처리합니다. 이로써 코드 가독성과 안전성이 높아집니다.
모듈(Module)과 크레이트(Crate) 구조
C++에서는 코드를 헤더와 소스 파일로 나누고, 네임스페이스를 통해 논리적 구조를 잡는 반면, 러스트는 모듈(module)과 크레이트(crate) 개념을 도입해 관리합니다.
- 크레이트(Crate): 독립적인 패키지 단위로, Cargo를 통해 관리되는 빌드 단위입니다. 하나의 라이브러리(lib)나 실행 가능한 바이너리(bin)를 만든다고 생각하면 됩니다.
- 모듈(Module): 크레이트 내에서 파일과 디렉토리를 논리적으로 구성하는 단위입니다. mod 키워드로 모듈을 정의하고, pub 키워드로 공개 범위를 지정할 수 있습니다.
예를 들어, src 디렉토리 내에 lib.rs가 있다면, 다음과 같이 모듈을 정의할 수 있습니다.
// lib.rs
mod sound {
pub mod instrument {
pub fn guitar() {
println!("기타 연주!");
}
}
}
fn main() {
crate::sound::instrument::guitar();
}
C++에서 헤더/소스 파일로 네임스페이스를 나누던 방식과 달리, 러스트에서는 모듈 시스템을 통해 네임스페이스와 접근 범위를 명확하게 제어합니다. 헤더 파일 없이 mod 키워드를 통해 다른 파일을 모듈로 인클루드하고, Cargo 프로젝트 구조를 따라가며 코드 조직을 합니다.
이는 C++의 전처리기 기반 헤더 인클루드 방식보다 더 안전하고 명확한 방식이며, 중복 선언이나 ODR(One Definition Rule) 문제를 걱정하지 않아도 됩니다.
C++와의 비교 정리
- 클래스 vs 구조체/impl: C++ 클래스와 유사하지만 상속 대신 trait로 다형성을 구현. 멤버 함수 정의는 impl 블록 사용.
- enum: C++의 enum과 달리 러스트 enum은 variant마다 다른 데이터 타입을 포함할 수 있어 매우 유연. std::variant와 유사한 기능을 언어 차원에서 제공.
- 패턴 매칭: C++ switch보다 강력한 match를 통해 구조적 패턴 매칭 지원. 복잡한 데이터 구조를 안전하고 직관적으로 분기 처리.
- 모듈 시스템: 헤더/소스 분리와 네임스페이스 대신 모듈과 크레이트 개념으로 프로젝트 구조화. Cargo를 통한 패키지 관리로 빌드와 구조 관리가 단순화.
앞으로의 학습 방향
이번 글에서는 구조체, 열거형, 패턴 매칭, 모듈 구조를 통해 러스트가 타입 정의와 코드 구조화를 어떻게 다루는지 알아보았습니다. 다음 글에서는 트레이트(Trait)와 제네릭(Generic)을 살펴보며, 러스트에서 다형성과 추상화가 어떻게 이루어지는지 C++ 템플릿 및 인터페이스 개념과 비교해보겠습니다.
유용한 링크와 리소스
- Rust Book: Structs: https://doc.rust-lang.org/book/ch05-00-structs.html
- Rust Book: Enums and Pattern Matching: https://doc.rust-lang.org/book/ch06-00-enums.html
- Rust 모듈 시스템: https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html
- C++ std::variant와 std::visit: https://en.cppreference.com/w/cpp/utility/variant
'개발 이야기 > Rust (러스트)' 카테고리의 다른 글
러스트 언어 입문 시리즈 - 7편: 에러 처리(Error Handling)와 Result 타입, Option 타입 비교하기 (0) | 2024.12.12 |
---|---|
러스트 언어 입문 시리즈 - 6편: 트레이트(Trait)와 제네릭(Generic)을 통한 추상화와 다형성 (0) | 2024.12.11 |
러스트 언어 입문 시리즈 - 4편: 컬렉션, 슬라이스, 이터레이터를 통한 실습 예제 (0) | 2024.12.09 |
러스트 언어 입문 시리즈 - 3편: 소유권(Ownership)과 빌림(Borrowing), 라이프타임(Lifetime)의 이해 (0) | 2024.12.08 |
러스트 언어 입문 시리즈 - 2편: Cargo를 활용한 프로젝트 시작하기 (1) | 2024.12.07 |