이 글은 Claude Opus 4.5 을 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.

Go 개발자로서 새로운 언어를 배우려면 먼저 그 언어가 왜 존재하는지, 어떤 문제를 해결하려고 하는지 이해하는 것이 중요합니다. 이 섹션에서는 Rust의 설계 철학을 Go와 비교하며 이해하고, 개발 환경을 설정한 뒤 첫 프로젝트를 생성해봅니다.


1.1 Rust는 왜 배워야 하는가?#

Rust의 설계 철학#

Rust는 Mozilla Research에서 시작되어 2015년에 1.0이 릴리스된 시스템 프로그래밍 언어입니다. Rust의 핵심 목표는 세 가지입니다:

  1. 안전성(Safety): 메모리 안전성을 컴파일 타임에 보장
  2. 속도(Speed): C/C++에 필적하는 성능
  3. 동시성(Concurrency): 데이터 레이스 없는 동시성 프로그래밍

Go도 비슷한 시기(2009년)에 등장했지만, 설계 목표가 다릅니다:

측면 Go Rust
주요 목표 단순성, 빠른 컴파일, 쉬운 동시성 안전성, 성능, 제로 비용 추상화
메모리 관리 가비지 컬렉션 (GC) 소유권 시스템 (컴파일 타임)
학습 곡선 완만함 가파름 (특히 소유권)
런타임 런타임 포함 (GC, 스케줄러) 최소 런타임 (거의 없음)
추상화 수준 실용적 단순함 고수준 추상화 가능

Go vs Rust: 설계 목표의 차이#

Go의 철학: “Less is more”

  • 언어 기능을 최소화하여 학습과 유지보수를 쉽게
  • 암묵적 인터페이스 구현으로 유연성 제공
  • 고루틴과 채널로 동시성을 쉽게
  • 빠른 컴파일 속도 우선

Rust의 철학: “Zero-cost abstractions”

  • 고수준 추상화를 사용해도 성능 손실 없음
  • 컴파일러가 최대한 많은 오류를 잡아줌
  • 명시적인 것을 선호 (암묵적 동작 최소화)
  • 안전성을 위해 학습 비용 감수
// Go: 간결하고 읽기 쉬움
func sum(numbers []int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}
// Rust: 더 명시적이지만 더 많은 보장
fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

// 또는 Go 스타일로 작성할 수도 있음
fn sum_imperative(numbers: &[i32]) -> i32 {
    let mut total = 0;
    for n in numbers {
        total += n;
    }
    total
}

Rust가 빛나는 영역#

1. 시스템 프로그래밍

  • 운영체제, 디바이스 드라이버, 임베디드 시스템
  • Linux 커널에 Rust 지원 추가됨
  • Windows도 일부 컴포넌트를 Rust로 재작성

2. WebAssembly

  • Rust는 WebAssembly의 1급 지원 언어
  • wasm-pack, wasm-bindgen 등 풍부한 도구
  • 브라우저에서 네이티브 성능

3. 고성능 네트워크 서비스

  • Discord: Go에서 Rust로 마이그레이션하여 지연 시간 개선
  • Cloudflare: 엣지 컴퓨팅에 Rust 사용
  • AWS: Firecracker (서버리스 VM) Rust로 개발

4. 암호화 및 보안

  • 메모리 안전성이 보안에 직결
  • 버퍼 오버플로우, use-after-free 등 원천 차단

5. CLI 도구

  • ripgrep (grep 대체): Go의 어떤 구현보다 빠름
  • fd (find 대체), bat (cat 대체)
  • 단일 바이너리 배포 (Go와 동일한 장점)

언제 Go를, 언제 Rust를 선택할 것인가#

Go를 선택해야 할 때:

  • 빠른 개발과 프로토타이핑이 중요할 때
  • 팀의 학습 곡선을 최소화해야 할 때
  • 마이크로서비스, API 서버 개발
  • DevOps 도구 개발
  • GC 일시 정지가 문제되지 않는 경우

Rust를 선택해야 할 때:

  • 예측 가능한 지연 시간이 필요할 때 (GC 없음)
  • 메모리 사용량을 정밀하게 제어해야 할 때
  • C/C++ 수준의 성능이 필요할 때
  • 시스템 레벨 프로그래밍
  • WebAssembly 타겟
  • 장기 실행 프로세스에서 메모리 관리가 중요할 때

1.2 개발 환경 설정#

rustup을 통한 설치#

Rust는 rustup이라는 도구체인 관리자를 사용합니다. Go의 버전 관리가 수동적인 것과 달리, rustup은 여러 버전을 쉽게 관리할 수 있습니다.

# macOS / Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Windows는 rustup-init.exe 다운로드
# https://rustup.rs 방문

# 설치 확인
rustc --version
cargo --version

rustup 주요 명령어:

# 최신 버전으로 업데이트
rustup update

# 설치된 도구체인 확인
rustup show

# nightly 버전 설치 (실험적 기능 사용 시)
rustup install nightly

# 기본 도구체인 변경
rustup default stable

# 프로젝트별 도구체인 설정
rustup override set nightly

Cargo: Rust의 빌드 시스템이자 패키지 매니저#

Cargo는 Go의 go 명령어와 go mod를 합친 것과 비슷합니다. 빌드, 테스트, 의존성 관리를 모두 담당합니다.

Go vs Cargo 명령어 비교:

작업 Go Cargo
새 프로젝트 생성 go mod init cargo new
빌드 go build cargo build
실행 go run . cargo run
테스트 go test ./... cargo test
의존성 추가 go get cargo add (cargo-edit)
포맷팅 go fmt cargo fmt
린트 go vet / golangci-lint cargo clippy
문서 생성 go doc cargo doc
릴리스 빌드 go build -ldflags="-s -w" cargo build --release

IDE 설정#

VS Code + rust-analyzer (권장):

# VS Code 확장 설치
# 1. rust-analyzer (공식 권장)
# 2. Even Better TOML (Cargo.toml 편집용)
# 3. Error Lens (인라인 에러 표시)
# 4. CodeLLDB (디버깅용)

settings.json 권장 설정:

{
    "rust-analyzer.checkOnSave.command": "clippy",
    "rust-analyzer.inlayHints.typeHints.enable": true,
    "rust-analyzer.inlayHints.parameterHints.enable": true,
    "[rust]": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "rust-lang.rust-analyzer"
    }
}

JetBrains RustRover:

  • JetBrains의 Rust 전용 IDE
  • IntelliJ 플랫폼 기반
  • GoLand 사용자라면 익숙한 환경

유용한 Cargo 확장 도구#

# 필수 도구들
rustup component add clippy      # 린터
rustup component add rustfmt     # 포매터

# 유용한 cargo 확장
cargo install cargo-edit         # cargo add/rm/upgrade
cargo install cargo-watch        # 파일 변경 감지 자동 빌드
cargo install cargo-expand       # 매크로 확장 보기
cargo install cargo-audit        # 보안 취약점 검사
cargo install cargo-outdated     # 의존성 업데이트 확인

cargo-watch 활용:

# Go의 air나 reflex처럼 파일 변경 시 자동 실행
cargo watch -x run

# 테스트 자동 실행
cargo watch -x test

# 여러 명령 조합
cargo watch -x check -x test -x run

1.3 첫 번째 Rust 프로젝트#

Hello, World!#

# 새 프로젝트 생성
cargo new hello_rust
cd hello_rust

# 프로젝트 구조
# hello_rust/
# ├── Cargo.toml
# └── src/
#     └── main.rs

Go 프로젝트와 비교:

# Go 프로젝트          # Rust 프로젝트
myproject/             myproject/
├── go.mod             ├── Cargo.toml
├── go.sum             ├── Cargo.lock
├── main.go            └── src/
└── pkg/                   └── main.rs
    └── util/
        └── util.go

Cargo.toml (Go의 go.mod에 해당):

[package]
name = "hello_rust"
version = "0.1.0"
edition = "2021"

[dependencies]
# 여기에 의존성 추가

src/main.rs:

fn main() {
    println!("Hello, world!");
}

Go와 비교해봅시다:

// Go
package main

import "fmt"

func main() {
    fmt.Println("Hello, world!")
}
// Rust
fn main() {
    println!("Hello, world!");
}

차이점:

  1. Rust는 package 선언 불필요 (파일 구조로 결정)
  2. println!은 함수가 아닌 매크로 (!가 매크로 표시)
  3. import 대신 use (필요할 때)
  4. 세미콜론 필수 (대부분의 경우)

빌드와 실행#

# 개발 빌드 (최적화 없음, 빠른 컴파일)
cargo build
# 실행 파일: target/debug/hello_rust

# 실행
cargo run

# 릴리스 빌드 (최적화, 느린 컴파일)
cargo build --release
# 실행 파일: target/release/hello_rust

# 문법 검사만 (빌드보다 빠름)
cargo check

Go와 빌드 속도 비교:

  • Go: 매우 빠른 컴파일 (설계 목표)
  • Rust: 상대적으로 느린 컴파일 (많은 검사와 최적화)

하지만 cargo check를 활용하면 개발 중 피드백 루프를 빠르게 유지할 수 있습니다.

라이브러리 프로젝트#

Go에서 main 패키지와 라이브러리 패키지가 다르듯, Rust도 바이너리와 라이브러리를 구분합니다.

# 라이브러리 프로젝트 생성
cargo new mylib --lib

# 구조
# mylib/
# ├── Cargo.toml
# └── src/
#     └── lib.rs    # main.rs 대신 lib.rs

바이너리 + 라이브러리 혼합:

myproject/
├── Cargo.toml
└── src/
    ├── main.rs    # 바이너리 진입점
    └── lib.rs     # 라이브러리 루트

1.4 REPL과 빠른 실험 환경#

Rust Playground#

play.rust-lang.org에서 브라우저로 Rust를 실험할 수 있습니다.

기능:

  • Stable/Beta/Nightly 버전 선택
  • 코드 공유 링크 생성
  • ASM/LLVM IR/MIR 출력 확인
  • Clippy/Miri 실행

cargo run –example#

프로젝트에 예제 코드를 포함할 수 있습니다:

myproject/
├── Cargo.toml
├── src/
│   └── lib.rs
└── examples/
    ├── basic.rs
    └── advanced.rs
# 특정 예제 실행
cargo run --example basic

# 모든 예제 나열
cargo run --example

evcxr: Rust REPL#

Python이나 Node.js처럼 대화형 환경을 원한다면:

# 설치
cargo install evcxr_repl

# 실행
evcxr

>> let x = 42;
>> x * 2
84
>> :type x
i32

Jupyter Notebook 커널도 있습니다:

cargo install evcxr_jupyter
evcxr_jupyter --install

1.5 Go 개발자가 알아야 할 Rust 생태계#

crates.io vs pkg.go.dev#

측면 Go (pkg.go.dev) Rust (crates.io)
중앙 저장소 없음 (분산) 있음 (중앙 집중)
버전 관리 Git 태그 기반 SemVer 엄격 적용
문서 pkg.go.dev docs.rs (자동 생성)
이름 충돌 경로로 구분 고유 이름 필요

필수 크레이트 소개#

1. serde - 직렬화/역직렬화

Go의 encoding/json과 비슷하지만 더 유연합니다.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct User {
    name: String,
    age: u32,
    #[serde(rename = "emailAddress")]  // Go의 `json:"emailAddress"`와 동일
    email: String,
}

fn main() {
    let user = User {
        name: "Alice".to_string(),
        age: 30,
        email: "alice@example.com".to_string(),
    };
    
    // 직렬화
    let json = serde_json::to_string(&user).unwrap();
    println!("{}", json);
    
    // 역직렬화
    let parsed: User = serde_json::from_str(&json).unwrap();
    println!("{:?}", parsed);
}

2. tokio - 비동기 런타임

Go의 고루틴과 달리, Rust는 비동기 런타임이 별도입니다.

[dependencies]
tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() {
    let result = fetch_data().await;
    println!("{}", result);
}

async fn fetch_data() -> String {
    // 비동기 작업
    "data".to_string()
}

3. anyhow & thiserror - 에러 처리

[dependencies]
anyhow = "1.0"      # 애플리케이션용
thiserror = "1.0"   # 라이브러리용
// anyhow: Go의 fmt.Errorf와 비슷
use anyhow::{Context, Result};

fn read_config() -> Result<String> {
    std::fs::read_to_string("config.toml")
        .context("Failed to read config file")?;  // 컨텍스트 추가
    Ok("config".to_string())
}

// thiserror: 커스텀 에러 타입 정의
use thiserror::Error;

#[derive(Error, Debug)]
enum MyError {
    #[error("Invalid input: {0}")]
    InvalidInput(String),
    #[error("IO error")]
    Io(#[from] std::io::Error),
}

4. clap - CLI 파싱

Go의 flag 패키지나 cobra와 비슷합니다.

[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "myapp")]
#[command(about = "A sample CLI application")]
struct Args {
    /// Name of the user
    #[arg(short, long)]
    name: String,
    
    /// Verbose mode
    #[arg(short, long, default_value_t = false)]
    verbose: bool,
}

fn main() {
    let args = Args::parse();
    println!("Hello, {}!", args.name);
}

5. tracing - 로깅과 추적

Go의 log 패키지보다 구조화된 로깅을 제공합니다.

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
use tracing::{info, warn, error, instrument};

#[instrument]  // 함수 진입/종료 자동 로깅
fn process_request(user_id: u32) {
    info!(user_id, "Processing request");
    // ...
    warn!("Something might be wrong");
}

fn main() {
    // 구독자 초기화
    tracing_subscriber::fmt::init();
    
    process_request(42);
}

문서화 문화#

Rust 커뮤니티는 문서화를 매우 중시합니다:

  1. docs.rs: 모든 크레이트의 문서가 자동 생성
  2. 예제 코드: 문서 내 예제가 테스트로 실행됨
  3. README 중심이 아님: API 문서가 주요 문서
/// 두 숫자를 더합니다.
/// 
/// # Examples
/// 
/// ```
/// let result = mylib::add(2, 3);
/// assert_eq!(result, 5);
/// ```
/// 
/// # Panics
/// 
/// 오버플로우 시 패닉합니다 (debug 모드에서).
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
# 문서 생성 및 브라우저에서 열기
cargo doc --open

1.6 요약: Go에서 Rust로 전환 시 마인드셋#

받아들여야 할 변화#

  1. 컴파일러와 싸우기: Rust 컴파일러는 까다롭지만, 통과하면 런타임 에러가 크게 줄어듭니다.

  2. 소유권 사고방식: “이 데이터를 누가 소유하는가?“를 항상 생각해야 합니다.

  3. 명시성: Go보다 더 많은 것을 명시적으로 작성합니다.

  4. 느린 컴파일: 대신 런타임에 발견할 버그를 컴파일 타임에 잡습니다.

Go 경험이 도움되는 부분#

  1. 정적 타입: 타입 시스템에 익숙함
  2. 단일 바이너리: 배포 방식 유사
  3. 패키지 관리: 개념적으로 유사
  4. 에러 처리: 명시적 에러 처리 문화

다음 단계#

다음 섹션에서는 Rust의 기본 문법과 타입 시스템을 Go와 비교하며 학습합니다. 변수 선언, 기본 타입, 함수, 제어 흐름 등 익숙한 개념들이 Rust에서는 어떻게 다른지 알아봅니다.


연습 문제#

  1. 환경 설정: rustup을 설치하고 cargo new hello_rust로 프로젝트를 생성해보세요.

  2. 빌드 비교: 같은 Hello World 프로그램을 Go와 Rust로 작성하고, 바이너리 크기와 빌드 시간을 비교해보세요.

  3. Playground: play.rust-lang.org에서 다음 코드를 실행해보세요:

    fn main() {
        let numbers = vec![1, 2, 3, 4, 5];
        let sum: i32 = numbers.iter().sum();
        println!("Sum: {}", sum);
    }
    
  4. 문서 탐색: docs.rs에서 serde 크레이트의 문서를 탐색해보세요.