“새로운 기술을 배울 땐, 그 기술이 왜 탄생했는지부터 알아야 한다.”

1부에서 명세-학습 주도 개발(SLD) 방법론을 소개했고, 그 중심에 있는 툴인 SLD CLI를 Rust로 개발할 것이라고 했다.

이번 2부에서는 왜 러닝 커브 높은 Rust를 굳이 선택했는지, 그리고 Rust의 소유권 개념 앞에서 얼마나 고생했는지 남겨보려고 한다.

1. Node.js를 버리고 Rust를 선택한, 철학적/실용적 이유

CLI 툴 개발에 가장 빠르고 쉬운 선택지는 Node.js였지만, 이번에는 처음으로 적용하는 SLD 개발을 학습 그 자체의 명세로 보았다.

1-1. Rust CLI가 주는 개발 철학의 완성도

SLD의 핵심은 명세의 치밀함 (질문도 있지만)이다. 코드를 배포하고 실행하는 환경조차 명세에 영향을 받을 수 밖에 없다.

따라서 Rust를 사용하기로 한 이유는 다음과 같다.

  • 치밀한 명세와 엄격한 컴파일러: Rust의 메모리 안전성엄격한 컴파일러는 제 명세가 조금이라도 모호할 경우 통과시키지 않음…… 이는 개발자를 ‘동작만 하는 코드’ 에서 ‘왜 이렇게 동작해야 하는지’ 를 고려하게 만들어 고생하게 만든다…
  • 크로스 플랫폼 단일 바이너리: Node.js는 런타임 의존성이 있지만, Rust는 최종 산출물이 단일 실행 파일(Binary) 다. 어떤 Linux, Mac 환경에서도 sld-cli 파일 하나만 있으면 바로 실행된다. 이는 SLD 툴의 이식성과 활용도를 발전시키는 강점이라고 생각했다.
  • 개발 툴 의 안정성 측면에서 Node.js보다 Rust가 확실한 이점을 제공했다 (TypeScript보다 안전한 느낌, 런타임은 적어도 안뜬다).

1-2. TUI 개발에서 얻는 Front-End 로직 최소화 이점

CLI TUI 환경은 웹 브라우저가 제공하는 복잡한 DOM 조작이나 비동기 상태 동기화 문제를 대부분 회피하게 해준다.

  • 순수한 상태 관리에 집중: 웹에서는 ‘어떻게 화면을 효율적으로 업데이트할까’가 주된 관심사라면, TUI에서는 ‘사용자 입력이 Model을 어떻게 바꿀까’ 라는 순수한 상태 관리 로직 에만 집중할 수 있었다. (UI 구현 최소화)
  • 프론트엔드 로직 최소화: 덕분에 복잡한 프론트엔드 컴포넌트 생명주기나 렌더링 최적화 대신, SLD의 핵심인 명세 트리 조작 로직(재귀 탐색, YAML 저장) 의 구현에 집중할 수 있었다.

2. SLD CLI의 구조: Model-Handler-View (M-H-V)

TUI 애플리케이션 개발을 위해 M-V-U(Model-View-Update) 패턴을 가져와 실제 프로젝트 구조에 맞게 Model-Handler-View (M-H-V) 정도로 적용했다.

2-1. Model (app.rs / App 구조체)

역할: 애플리케이션의 모든 상태(State) 를 저장.

  • pub struct App: 현재 선택된 노드의 인덱스, 명세 트리 데이터, 상태 메시지(status_message) 등 모든 데이터가 여기에 포함됨.

2-2. View (ui.rs / ui 함수)

역할: 상태(Model)를 읽어 화면에 렌더링.

  • fn ui(f: &mut Frame, app: &App): 인자로 불변 참조(&App) 를 받아, ratatui 위젯을 이용해 화면을 그림. 여기서는 절대로 상태를 변경할 수 없다. (React/Vue의 순수 함수 컴포넌트와 동일한 원칙.)

2-3. Handler (handler.rs / handle_update 함수)

역할: 사용자 입력(Msg)을 받아 Model을 변경하는 비즈니스 로직을 처리.

  • pub fn handle_update(app: &mut App, msg: Msg): 인자로 가변 참조(&mut App) 를 받아, 상태 변경에 관련된 모든 복잡한 로직(YAML 저장, 노드 추가/삭제 등)을 수행.

2-4. MSG의 자세한 역할: 상태 변경의 명세

사용자 입력은 Msg 라는 Enum 으로 변환. Msg는 단지 “사용자의 의도” 를 나타내는 명세다.

// Msg Enum (사용자의 의도 명세)
#[derive(Debug)]
pub enum Msg {
    MoveUp,
    MoveDown,
    HandleEditContent, // Enter
    HandleAddChild,
    ToggleHelpMessage, // ? 키 입력
    // ...
}
  • main.rs : 키 입력을 읽고 해당 키에 맞는 Msg 생성
  • App::update : Msg를 받아 Handler로 전달
  • handler.rs : match msg 패턴을 통해 Msg에 대응하는 함수를 호출 -> &mut App 상태 변경

이 구조로 데이터의 흐름을 단방향으로 정의할 수 있었으며, TUI에서 적어도 큰 오류는 발생하지 않았다.

3. Rust의 소유권에 처참히 당함

Rust의 안전성이라는 장점은 초보자에게는 가장 높은 진입 장벽이었다.

특히 소유권(Ownership)과 빌림(Borrowing) 개념 앞에서 수많은 컴파일 에러를 피할 수 없었다.

‘소유권’에 오류로 붙잡힌 사례

SLD 툴에서 명세 노드를 재귀적으로 탐색하는 로직이 핵심이다.

노드를 탐색하면서 동시에 App 구조체 내부의 다른 필드를 참조하거나 상태를 변경해야 할 때 문제가 발생했다.

  1. 동시 가변 빌림 불가: 노드 탐색 함수에서 App의 가변 참조(&mut App) 를 빌려 노드의 상태를 변경하려 할 때, 컴파일러가 “이미 다른 곳에서 빌리고 있으니 안 됨.” 이라고 오류를 띄운다. (특히, 트리를 탐색하며 다른 노드를 수정하는 과정에서 이 문제가 빈번했다.)

  2. 데이터 소유권 이동: 함수에 데이터를 넘길 때 소유권이 이동해버려서, 함수 호출 후 원본 데이터에 접근하려 하면 “이미 소유권이 넘어갔다”는 에러가 발생했다. 이 문제를 해결하기 위해 복사(clone())하거나 명시적인 참조(&) 를 이용해야 했다.

이 과정은 마치 “간단하게 설명할 수 없다면 모르는 것이다”라는 원칙을 코드로 강요당하는 듯한 기분이었다.

컴파일러가 오류를 낼 때마다 “데이터의 생명주기”“어디서 누가 이 데이터를 변경할 권한을 가졌는지”를 엄밀하게 재검토해야 했고, 이는 곧 SLD의 명세/학습 원칙을 Rust 코드로 적용하는 과정이었다.

  1. 아직 해결되지 않은 문제와 컴파일의 확실한 장점

** 얻은 것**

Rust 컴파일의 장점은 확실했다.

확실한 장점: 빌드에 성공하는 순간, 그 코드는 메모리 안전성과 데이터 경쟁 부재를 보장받는다(고 함).

즉, 런타임 시점에 소유권 문제로 프로그램이 터질 걱정은 없다는 확신을 준다. (실제로 런타임으로 터지지는 않았다.)

1. 앞으로의 학습 계획 및 SLD CLI 명세 추가

SLD CLI는 아직 초기 단계이며, 앞으로의 학습과 개발 계획은 다음과 같다.

  1. 추가 학습 계획 (기술/개념)

더 깊은 TUI 이벤트 처리: 현재는 키 이벤트만 처리하지만, 마우스 이벤트나 리사이즈 이벤트를 안정적으로 처리하는 방법을 연구하여 TUI의 UX를 개선할 수 있으면 좋을 것 같다.

매크로 시스템: 반복되는 코드를 줄이기 위해 Rust의 매크로 시스템을 학습하여 코드를 더 간결하게 만들어보기. (메타 프로그래밍이라고 하는데, 쉬운 개념은 아닌 것 같다.)

  1. SLD 명세 추가 (도구의 기능 개선)

SLD는 학습 기록 관리가 핵심인 만큼, 외부 편집기 활용 기능을 명세에 추가하여 사용성을 극대화해보자.

외부 편집기 연동 명세: 현재 선택된 노드의 학습/메모 파일을 Vim이나 Nano 같은 외부 편집기로 열어 편집하고, 저장 시 TUI로 자동 복귀하여 명세가 갱신되도록 하는 기능을 추가해보려고 한다. 마치 git commit 과 비슷한 느낌으로.

마무리하며

SLD CLI 개발은 Rust의 ‘왜?’라는 질문과 SLD의 ‘왜?’라는 질문이 만난 재귀적인 학습 과정이었다.

소유권 앞에서 고통받았지만, 대신 코드의 신뢰성과 시스템 아키텍처에 대한 이해는 새로운 도전을 통해 얻은 값진 지식이라고 생각한다.

후…

기능 개발 + 리팩터링 생각에 벌써 아찔하다

만약 궁금하다면?

Git 레포 바로가기