Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

16-1. 심화 사례: 수명 기반 빌림과 상태 구조체 설계 🔴

학습 목표:

  • C++의 위험한 생포인터(Raw Pointer) 저장 패턴을 Rust의 안전한 수명(Lifetime) 기반 빌림 시스템으로 전환하는 방법을 배웁니다.
  • 모든 기능을 독점하는 '거대 객체(God Object)'를 작고 독립적인 조합 가능한 상태 구조체로 분해하여 유지보수성을 극대화합니다.
  • 플러그인 시스템 등 확장이 필요한 시점에 **트레이트 객체(Trait Objects)**를 적절히 활용하는 기준을 세웁니다.

1. 프레임워크 포인터 저장 vs 컨텍스트 빌림

C++에서는 하위 모듈이 상위 프레임워크를 참조하기 위해 생포인터를 내부에 저장하곤 합니다. 이는 프레임워크가 먼저 소멸될 경우 댕글링 포인터를 유발하는 시한폭탄이 됩니다.

  • C++ 방식: m_pFramework 포인터를 멤버 변수로 저장하고 무작정 신뢰함 (런타임 위험).
  • Rust 방식: 포인터를 저장하지 않고, 실행 시점(execute)에 필요한 데이터만 담은 **컨텍스트(Context)**를 짧게 빌려와 사용함 (컴파일 타임 안전).
#![allow(unused)]
fn main() {
// Rust: 실행 시점에만 필요한 데이터를 짧게 빌려 쓰는 패턴
pub struct DiagContext<'a> {
    pub log: &'a mut EventLog,  // 프레임워크의 로그 시스템 빌림
    pub config: &'a Config,     // 설정값 읽기 전용 빌림
}

pub trait DiagModule {
    // 메서드 호출 시에만 컨텍스트를 넘겨받으므로 
    // 모듈 내부에 프레임워크 포인터를 영구 저장할 필요가 없습니다.
    fn execute(&mut self, ctx: &mut DiagContext) -> anyhow::Result<()>;
}
}

2. 거대 객체(God Object)의 해체와 조합(Composition)

시간이 흐를수록 모든 기능을 다 가진 거대한 클래스는 리팩터링이나 단위 테스트가 불가능해지는 '스파게티'가 됩니다.

  • 해결책: 관련 있는 필드끼리 묶어 별도의 상태 구조체로 분리하고, 메인 프레임워크는 이들을 조합하여 관리합니다.
#![allow(unused)]
fn main() {
// 기능별로 응집된 작은 상태 구조체들
struct GpuState { /* GPU 온도, 부하 등 */ }
struct NetworkState { /* 패킷 카운트, 지연 시간 등 */ }
struct HealthMonitor { /* 시스템 전체 건전성 지표 */ }

// 프레임워크는 이들을 조립(Composition)하는 역할만 수행
struct DiagFramework {
    gpu: GpuState,
    net: NetworkState,
    monitor: HealthMonitor,
}
}
  1. 테스트 용이성: GpuState만 따로 떼어내서 목(Mock) 데이터를 넣어 테스트할 수 있습니다.
  2. 영향도 분리: GPU 로직을 수정하다가 실수로 네트워크 엔진을 망가뜨리는 사고를 방지합니다.
  3. 가독성: self.gpu.temp와 같이 데이터의 소속과 의도가 명확해집니다.

3. 트레이트 객체(dyn Trait)의 황금률

모든 것을 열거형(enum)으로 처리할 수는 없습니다. 새로운 모듈이 지속적으로 추가되는 플러그인 시스템 같은 경우엔 동적 디스패치(dyn Trait)가 정답입니다.

상황권장 패턴결정 이유
도형 (원, 사각형)enum Shape종류가 한정되어 있고 성능이 최우선일 때
진단 모듈들Box<dyn DiagModule>외부에서 새로운 로직을 계속 추가(확장)해야 할 때
테스트 시 DB&dyn Database실제 DB와 테스트용 Mock DB를 교체해야 할 때

💡 실무자의 조언: "직역의 유혹을 뿌리치세요"

C++의 vector<unique_ptr<Base>>를 보고 무의식적으로 Vec<Box<dyn Trait>>를 쓰지 마세요. "이 타입들이 정말로 무한히 확장되어야 하는가?"라고 자문해 보세요. 만약 정해진 몇 가지 타입뿐이라면 **열거형(Enum)**이 성능과 메모리 효율, 안전성 면에서 압승입니다.


📌 요약

  • 컨텍스트 빌림 패턴으로 댕글링 포인터의 싹을 자르세요.
  • 거대 클래스는 작은 상태 구조체들의 조합으로 분해하세요.
  • **dyn Trait**는 꼭 확장이 필요한 시점에만 선별적으로 도입하세요.
  • 데이터와 행위의 소유권을 명확히 분리하는 것이 Rust 설계의 핵심입니다.