셋의 본질
| 개념 | 역할 | 인스턴스 |
|---|---|---|
| namespace | 이름의 그룹화 (충돌 방지 + 조직화) | ❌ 없음 |
| struct | 데이터의 묶음 (필드를 한 단위로) | ✓ 있음 |
| class | 데이터 + 행동 + 상속의 통합 (OOP 단위) | ✓ 있음 |
가장 결정적 차이 한 줄: namespace는 인스턴스가 없다. 코드 조직의 컴파일 타임 개념일 뿐. struct/class는 메모리에 살아있는 데이터 표현.
핵심 비교
| 특성 | namespace | struct | class |
|---|---|---|---|
| 인스턴스 생성 | ❌ | ✓ | ✓ |
| 데이터 필드 | ❌ | ✓ | ✓ |
| 메서드 | ❌ | 언어에 따라 | ✓ |
| 상속 | ❌ | ❌ (대체로) | ✓ |
| 메모리 위치 | 런타임 없음 | stack/heap | 보통 heap |
| 주된 역할 | 이름 충돌 방지 | 데이터 묶음 | 데이터 + 행동 + 다형성 |
언어별 채택 방식
| 언어 | namespace | struct | class |
|---|---|---|---|
| C | ❌ (prefix로 흉내) | ✓ | ❌ |
| C++ | ✓ namespace | ✓ (class와 거의 동일) | ✓ |
| Java | ✓ package | ❌ (record로 보강) | ✓ |
| Python | ✓ module/package | ❌ (@dataclass로 흉내) | ✓ |
| Rust | ✓ mod | ✓ | ❌ (분해됨) |
C++의 struct와 class는 거의 동일 — default 접근 제어자가 public(struct)/private(class)인 차이뿐. 진짜 다른 건 namespace.
Java는 데이터만 묶고 싶어도 class를 만들어야 해서 boilerplate가 많다는 비판을 받아 record를 도입.
Rust의 분해
Rust 디자인의 핵심은 "class라는 큰 추상화를 더 작고 독립적인 개념들로 분해했다". 다른 언어가 class에 묶어둔 기능들을 따로따로 떼어내 책임이 명확한 메커니즘들로 만든 것.
| 다른 언어의 class가 하던 일 | Rust 담당 |
|---|---|
| 데이터 묶기 | struct |
| 데이터에 행동 붙이기 | impl 블록 |
| 공통 인터페이스/다형성 | trait |
| 이름 충돌 방지 / 코드 조직 | mod |
| 상속 | (없음, composition + trait 대체) |
// 1. mod — namespace 만들기
mod geometry {
// 2. struct — 데이터 묶음
pub struct Rectangle {
pub width: f64,
pub height: f64,
}
// 3. impl — 데이터에 행동 붙이기
impl Rectangle {
pub fn new(w: f64, h: f64) -> Self {
Rectangle { width: w, height: h }
}
pub fn area(&self) -> f64 {
self.width * self.height
}
}
// 4. trait — 공통 인터페이스
pub trait Shape {
fn area(&self) -> f64;
}
// 5. trait 구현 (데이터 정의와 분리)
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
}
같은 도메인을 Java로:
package geometry;
interface Shape { double area(); }
public class Rectangle implements Shape {
public double width, height;
public Rectangle(double w, double h) { this.width = w; this.height = h; }
public double area() { return width * height; }
}
겉보기엔 비슷하지만 개념의 분리도가 다르다. Java의 class Rectangle은 데이터·메서드·인터페이스 구현을 한 선언에 다 넣지만, Rust는 데이터(struct), 메서드(impl), trait 구현(impl Trait for Type) 이 분리된다.
분해의 실용적 이점 — 직교성
기존 타입에 새 trait 구현 추가 가능. Java에서 String 클래스에 새 인터페이스 구현을 추가하려면 표준 라이브러리 소스를 고쳐야 한다(불가능). Rust는 자기 코드에 impl MyTrait for String { }이라고 적기만 하면 된다 — 데이터(struct)와 행동(impl)이 분리됐기 때문에 데이터 정의를 안 건드리고도 행동을 추가할 수 있다.
같은 데이터에 여러 impl 블록. struct 하나에 여러 impl 블록이 가능해서 책임별로 코드를 나눌 수 있다. 일반 메서드는 한 블록, trait 구현은 다른 블록.
namespace가 정말 namespace에만 집중. Java처럼 디렉토리 구조에 강제로 묶이지 않고, mod는 inline으로도 둘 수 있고, privacy 경계 역할도 명시적이다.
is-rust-object-oriented에서 본 "trait 지향(trait-oriented)" 결론이 여기서 구체화된다. Rust가 OOP를 거부한 게 아니라 OOP 개념들을 묶지 않고 분해해서 제공한 것이다. 사용자는 각 부품을 골라 조립해 OOP 스타일로도 짤 수 있고, 다른 스타일에선 부품을 다르게 쓸 수 있다.
결론
namespace는 이름의 그룹(인스턴스 없음), struct는 데이터의 묶음(인스턴스 있음, 데이터 위주), class는 데이터+행동+상속의 통합형. Rust는 class를 명시적으로 거부하고 그 기능을 struct(데이터) + impl(행동) + trait(인터페이스) + mod(namespace) 네 개의 직교한 메커니즘으로 분해했다. Java/C++의 class 한 덩어리가 Rust에선 네 개의 작은 개념으로 풀어진 셈이며, 처음엔 번거로워 보여도 각 부품을 독립적으로 조립할 수 있다는 유연함을 준다.
관련: what-is-namespace, what-are-methods-and-associated-functions, what-is-rust-enum, is-rust-object-oriented, rust-module-system-overview