# Go에서 Rust로 마이그레이션하기

> Clean Markdown view of GeekNews topic #29838. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29838](https://news.hada.io/topic?id=29838)
- GeekNews Markdown: [https://news.hada.io/topic/29838.md](https://news.hada.io/topic/29838.md)
- Type: GN+
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2026-05-25T08:19:56+09:00
- Updated: 2026-05-25T08:19:56+09:00
- Original source: [corrode.dev](https://corrode.dev/learn/migration-guides/go-to-rust/)
- Points: 1
- Comments: 1

## Topic Body

- **Go에서 Rust로의 전환**은 속도 향상보다 `nil`, 오류 처리, 데이터 레이스, 리소스 수명 문제를 컴파일 타임 보장으로 옮기는 선택에 가까움
- Go는 빠른 컴파일, 단순한 goroutine, 강한 백엔드 생태계가 장점이지만 Rust는 `Option`, `Result`, `Send`/`Sync`로 더 많은 실수를 타입 시스템에서 막음
- Rust의 **빌림 검사기**와 `async`/`await`는 학습 곡선과 사용성 비용을 만들며, 컴파일 시간도 Go보다 확실한 퇴보로 받아들여야 함
- 전환은 전체 재작성보다 **핫패스 서비스**, 워커, 게이트웨이 뒤의 일부 엔드포인트처럼 경계가 명확한 컴포넌트부터 적용하는 전략이 적합함
- 기대 효과는 CPU 20~60% 감소, 메모리 30~50% 감소, 더 평평한 P99 지연 시간, `nil` 역참조와 레이스성 장애 감소로 요약됨

---

### 전환의 초점
- Go에서 Rust로의 전환은 “Rust가 더 빠른가”보다 **정확성 보장**, 런타임 절충, 개발자 경험의 차이를 따지는 문제에 가까움
- 비교의 중심은 **백엔드 서비스**이며, Go가 강한 작은 정적 바이너리, 네트워킹 중심 표준 라이브러리, HTTP 서버·gRPC·데이터베이스 생태계를 기준으로 삼음
- CLI 도구, 임베디드 펌웨어, 게임 엔진에도 일부 내용이 적용될 수 있지만 최적화된 대상은 아님
- 관련 배경 자료로 2017년 [“Go vs Rust? Choose Go.”](https://endler.dev/2017/go-vs-rust/)와 Shuttle 팀의 [“Rust vs Go: A Hands-On Comparison”](https://www.shuttle.dev/blog/2023/09/27/rust-vs-go-comparison)이 제시됨
- Go는 성공한 언어지만 `nil`의 광범위한 사용, 타입이 아닌 규율에 의존하는 오류 처리, 오랫동안 없었던 제네릭 같은 설계 선택은 Rust와 비교할 때 주요 쟁점이 됨
- JetBrains Developer Ecosystem Survey에서 Go는 작업 개발자 비중이 **17~19%** 수준을 유지하는 언어로 제시되고, Rust는 꾸준히 성장하지만 더 작은 비중으로 나타남

### 도구 체계
- Go와 Rust는 모두 빌드, 테스트, 포맷, 린트, 의존성 관리를 일관된 인터페이스로 제공하는 **배터리 포함 도구 체계**를 갖춤
- `cargo`는 Go 도구와 대응되는 기능을 1차 도구로 더 넓게 제공함
  - `go.mod` / `go.sum` → `Cargo.toml` / `Cargo.lock`: 프로젝트 설정과 의존성 매니페스트
  - `go get` / `go mod tidy` → `cargo add` / `cargo update`: 의존성 추가와 해석
  - `go build` → `cargo build`: 컴파일
  - `go run .` → `cargo run`: 빌드 후 실행
  - `go test ./...` → `cargo test`: 테스트
  - `go vet ./...` → `cargo clippy`: 린터이며, `Clippy`는 `vet`보다 훨씬 더 의견이 강함
  - `gofmt` / `goimports` → `cargo fmt`: 무설정 자동 포매터
  - `golangci-lint run` → `cargo clippy -- -D warnings`: 엄격한 린트 모드
  - `go doc` → `cargo doc --open`: API 문서 생성과 열람
  - `pprof` → `cargo flamegraph` / `samply`: CPU 프로파일링
  - `govulncheck` → `cargo audit`: 권고 데이터베이스 기반 취약점 검사
- Go에서는 `golangci-lint`, `mockgen`, `air`, `goreleaser` 같은 서드파티 도구로 빈틈을 메우는 경우가 많지만, Rust는 1차 생태계가 더 많은 기능을 기본으로 포괄함
- 외부 크레이트가 필요한 경우에도 `cargo watch`, `cargo nextest`처럼 `cargo install cargo-nextest` 한 번으로 설치되고 `cargo nextest`처럼 네이티브 도구처럼 동작함
- `gofmt`와 `rustfmt`는 세부 스타일 취향보다 코드 리뷰의 스타일 논쟁을 없애는 이점이 더 큼
  - Rob Pike의 Go Proverbs 인용: “Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.”

### Go와 Rust의 핵심 차이
- 두 언어는 모두 컴파일 언어, 정적 타입, 단일 바이너리 배포, 강한 동시성 모델을 제공하지만 차이는 **컴파일러가 보장하는 범위**와 런타임 동작 제어 수준에 있음
- 주요 비교 항목은 다음과 같음
  - 안정 릴리스: Go 2012년, Rust 2015년
  - 타입 시스템: Go는 정적·구조적 타입이며 1.18부터 제네릭 지원, Rust는 정적·명목적 타입이며 제네릭·트레이트·수명 지원
  - 메모리 관리: Go는 동시·저지연 가비지 컬렉션, Rust는 소유권과 대여 기반이며 GC 없음
  - 널 안전성: Go는 `nil`이 널리 존재, Rust는 널이 없고 `Option&lt;T&gt;`가 타입 수준 대체재
  - 오류 처리: Go는 `error` 인터페이스와 `if err != nil { ... }`, Rust는 `Result<T, E>`, `?` 연산자, 완전한 패턴 매칭
  - 동시성: Go는 goroutine과 채널 기반 CSP, Rust는 `tokio` 위의 `async`/`await`, 채널, 스레드
  - 취소: Go는 관례 기반 `context.Context`, Rust는 `CancellationToken` 등 명시적이고 타입 검사되는 전달
  - 데이터 레이스: Go는 `-race`로 런타임에 확률적으로 탐지, Rust는 `Send`/`Sync`로 컴파일 타임에 탐지
  - 컴파일 시간: Go는 매우 빠르고, Rust는 특히 클린 빌드가 느림
  - 런타임: Go는 약 2MB 런타임과 GC, Rust는 `libc` 외 런타임이 없거나 MUSL로 완전 정적 빌드 가능
  - 생태계 규모: Go는 약 **75만+ 모듈**, Rust는 **25만+ 크레이트**
- Go에서 관례, 도구, 런타임 탐지에 의존하던 `nil` 처리, 오류 전파, 데이터 레이스, 리소스 수명, 취소, 제네릭 같은 검사가 Rust에서는 타입 시스템 안으로 들어감
- Rust의 `Mutex&lt;T&gt;`는 `.lock()`으로 얻은 가드를 통해서만 내부 값에 접근하게 만들어, “락을 잊는 경로” 자체를 타입에서 제거함
- 같은 패턴이 `Option`, `Result`, `&mut T`, `Send`/`Sync`, RAII 가드 전반에 반복되며, 익숙해지면 컴파일러가 머릿속 점검을 대신하는 형태가 됨

### Rust를 검토하게 만드는 Go의 한계
- Go가 대부분의 백엔드 워크로드에서 충분히 빠르기 때문에, Rust 검토의 주된 이유는 속도보다 **오류 처리의 장황함**, `nil` 포인터 위험, enum·trait 같은 정교한 타입 시스템 기능 부족에 가까움
- Go 인터페이스는 Rust 트레이트의 충분한 대체물이 아니며, 표준 라이브러리에 `Set` 타입이 없어 `map[T]struct{}` 같은 관용적 우회가 필요함
- ## 프로덕션의 `nil` 패닉
  - Go 서비스는 몇 달간 정상 동작하다가 특정 코드 경로에서 포인터 `nil` 확인을 놓쳐 goroutine 패닉을 낼 수 있음
  - 예시에서는 `Find`가 `(*User, error)`를 반환하고 “not found”에서 `error`는 `nil`이지만 `user` 확인은 호출자 몫으로 남음
  - `user.Account.Notify()`는 `user` 또는 `Account`가 `nil`일 때 크래시될 수 있음
  - `nilaway`, `staticcheck` 같은 린터와 IDE 검사는 일부를 잡지만, 옵트인이고 확률적이며 패키지 경계를 안정적으로 넘지 못함
  - Rust의 `Option&lt;T&gt;`는 `None` 경우를 처리하지 않고는 역참조할 수 없게 해 이 범주의 장애를 제거함
- ## `-race`가 잡지 못한 데이터 레이스
  - `go test -race`는 훌륭한 도구지만 런타임 탐지기이므로 테스트 중 실제 실행된 레이스만 찾음
  - Go에서는 두 goroutine이 락 없이 map을 변경하는 코드도 컴파일되고, 부하가 걸린 프로덕션에서 터질 수 있음
  - Rust에서는 스레드 간 가변 상태 공유에 `Send`와 `Sync` 구현 타입이 필요하며, 평범한 `HashMap`을 스레드 간 공유하려 하면 컴파일되지 않음
  - `Arc<Mutex<...>>`, `Arc<RwLock<...>>`, 채널 중 하나를 사용하도록 강제되며, 레이스 조건이 타입 오류가 됨
  - Paul Dix는 InfluxDB 3.0 재작성 동기로 데이터 레이스 제거를 직접 언급함
    - “[The main benefit is] fearless concurrency — eliminating data races essentially, which we had before. Really gnarly bugs in version 1 of Influx due to that.”
    - 출처: Paul Dix, Founder & CTO, InfluxData, [Rust in Production](https://corrode.dev/podcast/s01e01-influxdata?t=55%3A40)
- ## 조합 가능한 오류 처리
  - Go의 `if err != nil { return err }`는 함수의 실제 로직을 희석할 수 있고, `fmt.Errorf("doing X: %w", err)`로 맥락을 감싸는 일은 컴파일러 규칙이 아닌 규율에 의존함
  - [Lobste.rs 스레드](https://lobste.rs/s/g44oeq/rust_vs_go_hands_on_comparison)에서는 숙련된 Go 개발자들이 `errcheck`와 `golangci-lint`가 오류 처리 누락 대부분을 잡고, 명시적 `if err != nil`이 조밀한 `?` 체인보다 읽기 쉽다고 반박함
  - Peter Bourgon은 Go의 명시적 오류 처리를 의도된 문화적 가치로 제시함
    - “I think that error handling should be explicit, this should be a core value of the language.”
    - 출처: Peter Bourgon, [GoTime #91](https://changelog.com/gotime/91), Dave Cheney의 [Zen of Go](https://dave.cheney.net/2020/02/23/the-zen-of-go)에 인용
  - Rust의 `Result<T, E>`는 타입 시그니처 자체라서 잊을 수 없고, `thiserror::Error`로 정의한 enum과 `#[from]`을 통해 오류 변환과 완전성 검사를 받음
  - 새 오류 variant를 추가하면 컴파일러가 갱신이 필요한 `match` 위치를 알려줌
- ## 박싱하지 않는 제네릭
  - Go 1.18 제네릭은 유용하지만, 타입 파라미터가 있는 메서드 부재, GC shape stenciling, 가끔 놀라운 성능 특성 같은 제약이 있음
  - Rust 제네릭은 단형화되어 각 인스턴스화가 특수화된 코드를 만들고 런타임 비용이 없음
  - 트레이트와 결합하면 **제로 비용 추상화**가 가능함
  - 핸들러 코드보다는 미들웨어, generic repository, decoder, parser 같은 공유 인프라에서 더 중요하며, Go는 이런 영역에서 `interface{}`/`any`와 타입 단언으로 돌아가는 경우가 많음
- ## 예측 가능한 지연 시간
  - Go의 GC는 우수하고 동시적이며 저지연이고 일반적인 서비스 워크로드에 잘 튜닝되어 있지만, “low-pause”는 “no-pause”가 아님
  - 할당이 많은 상황에서는 핫패스에서 할당하지 않는 Rust 구현보다 P99 지연 시간 꼬리가 나빠질 수 있음
  - 트레이딩, 실시간 입찰, 네트워크 프록시, 고처리량 수집처럼 지연 시간에 민감한 시스템에서는 GC pause 부재가 실제 장점임
  - Stephen Blum은 PubNub 규모에서 필요한 가격 대비 성능 용량을 얻기 위해 Rust가 필요하다고 말함
    - “Go is great at our scale, but we really need something that is going to give us the price-per-dollar performance capacity that we need, and Rust is going to get us there. That’s why basically everything is heading towards Rust these days.”
    - 출처: Stephen Blum, CTO, PubNub, [Rust in Production](https://corrode.dev/podcast/s01e02-pubnub?t=17%3A25)

### Go 패턴의 Rust 대응
- Rust에 익숙해지는 빠른 방법은 이미 아는 Go 패턴을 Rust의 대응 패턴에 매핑하는 것임
- 동일한 백엔드 서비스를 두 언어로 구현한 더 긴 예시는 [Shuttle comparison](https://www.shuttle.dev/blog/2023/09/27/rust-vs-go-comparison)에 있음
- ## 오류 처리: `if err != nil` vs `Result<T, E>`
  - Go는 `os.ReadFile(path)`와 `json.Unmarshal` 뒤 `if err != nil`로 맥락을 감싼 오류를 반환함
  - Rust는 `fs::read_to_string(path)?`, `serde_json::from_str(&data)?`, `Ok(cfg)`로 구성됨
  - `?` 연산자는 `if err != nil { return err }` 패턴을 대신하며, `From&lt;E1&gt; for E2`가 구현되어 있으면 타입 변환까지 처리함
  - `thiserror`의 `#[from]`은 이 변환을 관용적으로 지원함
- ## 널: `nil` vs `Option&lt;T&gt;`
  - Go의 `GetUser(id string) *User`는 사용자를 찾지 못하면 `nil`을 반환하고, 호출자가 `fmt.Println(u.Name)`을 하면 `nil`일 때 패닉이 발생함
  - Rust의 `get_user(id: &str) -> Option&lt;User&gt;`는 `Some(User)` 또는 `None`을 반환함
  - `let user = get_user("123"); println!("{}", user.name);`는 `user`가 `User`가 아니라 `Option&lt;User&gt;`라서 컴파일 오류가 됨
  - `match get_user("123")`로 `Some(u)`와 `None`을 모두 처리해야 함
  - 안전한 Rust에는 `nil`이 없고, 참조는 null이 될 수 없음
- ## 인터페이스 vs 트레이트
  - Go 인터페이스는 구조적이며, 타입이 인터페이스를 암묵적으로 만족함
  - Rust 트레이트는 명목적이며, 명시적으로 구현해야 함
  - Go 방식은 즉석 duck typing에 좋고, Rust 방식은 리팩터링과 discoverability에 좋으며 특정 trait 구현체를 grep으로 찾을 수 있음
  - `fn handle&lt;R: Reader&gt;(r: R)` 같은 trait bound가 붙은 generic function이 대부분의 경우를 커버하며, 단형화로 런타임 디스패치가 없음
  - 런타임 디스패치가 필요한 이질적 구현체 저장에는 `Box&lt;dyn Trait&gt;` 또는 `Arc&lt;dyn Trait&gt;`을 사용함
- ## Goroutine vs async task
  - Go의 동시성 모델은 `go doWork(ctx, input)`처럼 단순하며, goroutine은 저렴하고 런타임이 OS 스레드 위에 스케줄링함
  - Go의 큰 장점은 **순차 코드와 병렬 코드 사이에 문법적 구분이 없다는 점**임
  - Rust는 백엔드 서비스에서 거의 항상 `tokio` executor 위의 `async`/`await`를 사용함
  - async 함수는 `Future`를 반환하며, await되거나 spawn되기 전에는 실행되지 않음
  - 컴파일러가 `.await` 지점 전후의 `Send`/`Sync`를 추적하고, non-`Send` 값을 await 너머로 보유하면 컴파일 오류를 냄
  - goroutine식 내장 선점이 없어서 CPU 바운드 작업을 async task 안에서 오래 실행하면 executor가 굶주릴 수 있으며, `tokio::task::spawn_blocking` 또는 `rayon`으로 넘겨야 함
- ## `context.Context` vs `CancellationToken`
  - Go에서는 모든 blocking call에 `context.Context`를 전달함
  - Rust에는 내장 `context.Context`가 없으며, 취소에 가장 가까운 대응물은 `tokio_util::sync::CancellationToken`임
  - timeout은 `tokio::time::timeout(dur, fut)`로 future를 감쌈
  - deadline과 값은 하나의 context 객체보다 명시적 인자나 `tracing` span을 통해 전달하는 경우가 많음
  - Dave Cheney의 [The Zen of Go](https://dave.cheney.net/2020/02/23/the-zen-of-go) 인용:
    - “Go doesn’t have a way to tell a goroutine to exit. There is no stop or kill function, for good reason. If we cannot command a goroutine to stop, we must instead ask it, politely.”
  - Go에서는 이 “정중한 요청”이 관례적으로 전달되는 `context.Context`이고, Rust에서는 `CancellationToken` 또는 `watch` 채널이지만 컴파일러가 누락을 알려줄 수 있음
- ## 문자열: `string` vs `String`과 `&str`
  - Go의 `string`은 UTF-8 byte slice이며, 대입 시 header가 복사되고 underlying bytes는 공유되는 불변 구조임
  - Rust는 이를 두 타입으로 나눔
    - `String`: 소유하고, heap에 할당되며, growable함
    - `&str`: 다른 문자열 데이터에 대한 borrowed view이며, 대부분의 경우 Go의 `string` parameter에 대응함
  - 경험칙은 인자에는 `&str`을 받고, 새 데이터를 만들 때는 `String`을 반환하는 것임
  - `&str`과 `String`의 분리는 Rust의 “borrow vs own” 모델을 축소해 보여줌

### Go 제네릭에 대한 평가
- Go는 1.18, 2022년 3월에 제네릭을 도입했으며, 언어 출시 **13년 뒤**였음
- 제네릭은 유용하지만 Rust·Haskell·현대 C++에서 기대하는 장점을 충분히 주지 못하고, 제네릭 타입 시스템의 단점 상당수를 함께 가진 것으로 평가됨
- ## 표준 라이브러리가 거의 쓰지 않음
  - 제네릭 도입 3년 뒤에도 Go 표준 라이브러리는 대부분 제네릭을 피함
  - `sort.Slice`는 여전히 `cmp.Ordered` constraint 대신 `func(i, j int) bool` closure를 받음
  - `sync.Map`은 여전히 `any`/`any`로 타입화됨
  - 존재하는 generic helper는 `slices`, `maps`, `cmp`, `sync` 아래 일부 항목 같은 소수 패키지에 있음
  - Go 1 호환성 약속 때문에 기존 non-generic API를 개조하기 어렵다는 점은 일부 설명이 되지만, Rust처럼 제네릭을 주된 도구로 쓰지는 않음
  - Rust는 초기부터 `Option&lt;T&gt;`, `Result<T, E>`, `Vec&lt;T&gt;`, `HashMap<K, V>`, `Iterator`, `From`/`Into`, 모든 컬렉션과 스마트 포인터에 제네릭이 스며 있음
- ## 트레이트 시스템이 없고 구조적 constraint만 있음
  - Rust 제네릭은 ad-hoc 다형성, supertrait, associated type, blanket impl, coherence를 담당하는 trait와 묶여 있음
  - Go constraint는 type-set membership을 위한 `~` 연산자가 추가된 interface에 가까움
  - Go에는 Rust의 `trait Ord: Eq + PartialOrd` 같은 supertrait hierarchy, `Iterator`의 `type Item;` 같은 associated type, `impl&lt;T: Display&gt; ToString for T` 같은 blanket impl이 없음
  - Go에서는 [타입 파라미터를 가진 메서드](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods)를 쓸 수 없어 `func (s Set[T]) Map[U](<https://corrode.dev/learn/migration-guides/go-to-rust/f func(T>) U) Set[U]` 같은 형태가 불가능함
  - 추상화가 “몇 가지 연산을 가진 임의의 `T`에 대해 동작하는 함수”를 넘는 순간, Go는 `any`, 타입 단언, 코드 생성, 런타임 reflection으로 돌아가게 됨
- ## 타입 추론과 구현 전략의 차이
  - Rust는 closure, iterator chain, `?` 연산자를 포함한 전체 expression에 타입 정보를 전파함
  - Go의 추론은 더 얕고, 보통 함수 인자에서 type parameter를 추론하지만 [return-position context에서는 추론할 수 없고](https://go.dev/blog/type-inference), 호출 지점에서 명시적 type argument를 자주 요구함
  - Go는 [GCShape stenciling and dictionaries](https://go.googlesource.com/proposal/+/refs/heads/master/design/generics-implementation-gcshape.md)라는 중간 경로를 택해 빠른 컴파일 시간을 유지하지만, type parameter의 메서드 호출마다 indirection이 들어갈 수 있음
  - 이를 보인 자료로 [PlanetScale 글](https://web.archive.org/web/20220331073738/https://planetscale.com/blog/generics-can-make-your-go-code-slower)이 제시됨
  - Rust는 `Vec&lt;i32&gt;`와 `Vec&lt;String&gt;` 각각에 특수화된 기계어 코드를 만들고 런타임 디스패치가 없음
  - 단형화의 대가는 컴파일 시간이며, 두 언어는 서로 다른 목표를 최적화함
- ## 타입 시스템의 구멍을 메우지 못함
  - Rust에서는 제네릭과 트레이트가 `Box&lt;dyn Any&gt;`나 런타임 reflection이 필요할 상황 대부분을 제거함
  - Go 제네릭은 `any`, `reflect`, ORM·decoder·mock에서 지배적인 코드 생성 패턴을 제거하지 못함
  - `encoding/json`은 여전히 reflection을 사용하고, `database/sql`은 여전히 `any`를 사용하며, `mockgen`은 여전히 코드를 생성함
  - Go의 제네릭은 좁은 경우에 유용한 새 도구처럼 느껴지고, Rust의 제네릭은 제거하면 언어가 무너지는 기초처럼 작동함

### Rust 백엔드 생태계
- Rust 생태계도 일반적인 백엔드 서비스에 대해 “기본 선택지”가 어느 정도 수렴해 있음
- 대표적인 대응 관계:
  - HTTP server: Go `net/http`, `chi`, `gin`, `echo`, `fiber` → Rust `axum` on `hyper`
  - HTTP client: Go `net/http`, `resty` → Rust `reqwest`
  - gRPC: Go `google.golang.org/grpc` + `protoc-gen-go` → Rust `tonic` + `prost`
  - SQL: Go `database/sql`, `sqlc`, `sqlx`, `gorm` → Rust `sqlx`, `sea-orm`, `diesel`
  - Migrations: Go `golang-migrate`, `goose` → Rust `sqlx migrate`, `refinery`
  - JSON: Go `encoding/json`, `sonic`, `goccy/go-json` → Rust `serde` + `serde_json`
  - Logging: Go `log/slog`, `zerolog`, `zap` → Rust `tracing` + `tracing-subscriber`
  - Metrics: Go `prometheus/client_golang` → Rust `metrics` + `metrics-exporter-prometheus`
  - Config: Go `viper`, `koanf` → Rust `config` / config-rs, `figment`
  - CLI: Go `cobra`, `urfave/cli` → Rust `clap` derive
  - Errors: Go `errors`, `pkg/errors` → Rust `thiserror` for libraries, `anyhow` for binaries
  - Testing: Go `testing`, `testify`, `gomega` → Rust built-in `#[test]`, `rstest`, `assert_matches`
  - Mocking: Go `mockgen`, `moq` → Rust hand-written fakes가 관용적이며 `mockall`도 사용
  - Background tasks: Go goroutines + `errgroup` → Rust `tokio::spawn` + `JoinSet`
- 전형적인 백엔드 서비스에서는 `axum` + `sqlx` + `tokio` + `tracing` + `serde` + `clap` 조합이 필요한 것의 **90%** 를 커버한다고 제시됨

### 빌림 검사기와 학습 곡선
- Go에서 Rust로 오면 [**벽에 부딪히게 됨**](https://corrode.dev/blog/flattening-rusts-learning-curve/)이라는 점을 전제로 삼아야 함
- Go 런타임은 메모리와 별칭(aliasing)을 대신 처리하지만, Rust는 그 결정을 타입 시스템으로 옮기기 때문에 처음 몇 주 동안은 “당연히 동작해야 할” 코드가 컴파일러에 거부될 수 있음
- Go 개발자가 자주 부딪히는 패턴:
  - 오래 사는 참조: Go에서는 맵에서 꺼낸 `*User`를 오래 들고 있어도 자연스럽지만, Rust에서는 그 빌림이 살아 있는 동안 맵 변경이 막힘
  - 자기 참조 구조체: Go에서는 데이터와 그 데이터 위의 반복자를 같은 구조체에 담을 수 있지만, Rust에서는 `Pin`, `ouroboros`, 또는 재설계가 필요함
  - goroutine 사이의 가변 상태 공유: Go의 `mu sync.Mutex; data map[K]V` 패턴은 Rust에서 `Arc<Mutex<HashMap<K, V>>>` 형태가 됨
  - 함수에서 참조 반환: [수명 주석](https://corrode.dev/blog/lifetimes/)이 등장하며, Go 개발자에게는 새로운 개념임
- **빌림 검사기**는 방해하는 “문지기”가 아니라 실제로 존재하는 버그를 드러내는 장치로 봐야 함
- 값이 이동된 뒤 다시 사용되거나, 여러 스레드가 같은 데이터를 동시에 만지거나, null·댕글링 포인터를 역참조하거나, 값보다 참조가 오래 사는 경우를 컴파일 시간에 걸러냄
- 빌림 개념을 내면화하면 싸우는 대상이 아니라 협력자로 바뀌며, 숙련 Rust 개발자들은 보통 4~12주 사이에 빌림 검사기가 조력자가 됐다고 말함
- PubNub CTO Stephen Blum은 [Rustacean Station](https://rustacean-station.org/)에서 첫 달을 “프로그래밍을 처음 배울 때 같았다”고 표현하며, 빌림 검사기와 수명을 강제로 다뤄야 했다고 말함
- `clap` 메인테이너 Ed Page는 [Rustacean Station: clap with Ed Page](https://rustacean-station.org/)에서 빌림 검사기가 고수준 문제에 집중할 수 있게 했고, 직접 분석에 실패한 부분도 잡아줬다고 말함

### Rust 전환의 주요 난관
- ## 컴파일 시간
  - **Rust 컴파일 시간**은 Go보다 확실한 퇴보로 받아들여야 하며, 중간 규모 서비스의 클린 릴리스 빌드는 Go의 거의 즉각적인 컴파일과 달리 몇 분이 걸릴 수 있음
  - 증분 빌드와 `cargo check`는 합리적이고 컴파일 시간도 해마다 개선됐지만, Go와의 차이는 체감됨
  - 편집 루프에서는 `cargo check`를 사용하고, 이득이 생기는 시점에 워크스페이스로 분리하며, 프로시저 매크로가 많은 크레이트는 별도 크레이트로 유지해 변경될 때만 다시 컴파일되게 함
  - 더 자세한 내용은 [Rust 컴파일 시간을 줄이는 팁](https://corrode.dev/blog/tips-for-faster-rust-compile-times/)을 참고할 수 있음
- ## 비동기 색칠 문제
  - Rust의 `async fn` / `fn` 분리는 Go에서 넘어올 때 가장 큰 사용성 퇴행 중 하나임
  - **async trait**은 Rust 1.75부터 안정화됐지만, 동적 디스패치와 섞을 때 여전히 거친 부분이 있음
  - 일부 상황에서는 이런 부분을 덮기 위해 `async-trait` 크레이트를 쓰게 됨
- ## 더 작은 생태계
  - Rust 크레이트 생태계는 성장 중이고 라이브러리 품질도 전반적으로 높지만, Go는 일부 백엔드 인접 영역에서 앞서 있음
  - **Go가 앞선 영역**에는 Kubernetes 오퍼레이터, 클라우드 제공자 SDK, 특정 틈새 저장소용 데이터베이스 드라이버가 포함됨
  - 마이그레이션을 확정하기 전에 하루 정도 시간을 들여 의존하는 라이브러리에 사용할 만한 Rust 대안이 있는지 확인해야 함
  - 일부 팀은 방치된 XML 스키마 검증 크레이트를 업데이트하거나 덜 알려진 프로토콜의 클라이언트를 직접 작성해야 할 수 있음

### 통합 전략
- Go에서 Rust로의 성공적인 전환은 한 번에 모두 다시 쓰는 방식이 아니라 전술적 선택에 가까움
- Microsoft Principal Engineer Victor Ciura는 [Rust in Production](https://corrode.dev/podcast/s04e01-microsoft)에서 “전부를 재미로 Rust로 다시 쓰는 게 아니라, 새 컴포넌트가 Rust에 더 적합하면 Rust로 하는 전술적 선택”이라고 말함
- ## 1. 핫패스를 서비스로 떼어내기
  - 특정 서비스가 계속 문제를 일으키는 경우, 같은 API 계약 뒤에 두고 해당 서비스만 Rust로 다시 쓰는 방식이 가장 낮은 위험의 마이그레이션임
  - 대상은 CPU 사용량이 높거나, 지연 시간에 민감하거나, 안정성 문제가 반복되는 서비스일 수 있음
  - 다른 Go 서비스는 HTTP/gRPC로 계속 통신하므로 내부 구현 언어를 몰라도 됨
  - Radar CTO Jeff Kao는 [Rust in Production](https://corrode.dev/podcast/s05e08-radar)에서 [Discord가 Go에서 Rust로 이동한 글](https://discord.com/blog/why-discord-is-switching-from-go-to-rust)이 Radar에도 같은 시도를 떠올리게 했다고 말함
- ## 2. 사이드카나 워커 프로세스 교체
  - 백그라운드 워커, 큐 소비자, 수집 파이프라인, CPU 바운드 배치 작업은 좋은 첫 대상임
  - 보통 큐나 토픽 같은 명확한 입력/출력 경계를 가지며, 시스템의 나머지와 인프로세스 공유 상태가 없음
- ## 3. cgo는 가능하지만 고통스러움
  - Go에서 cgo를 통해 Rust를 호출할 수 있고, [이를 다루는 좋은 가이드](https://blog.arcjet.com/calling-rust-ffi-libraries-from-go/)도 있음
  - 백엔드 서비스에서는 보통 권장되지 않음
  - 빌드 복잡성과 FFI 오버헤드가 “Rust 서비스를 세우고 네트워크 호출 뒤에 두는” 방식보다 이점을 상쇄하는 경우가 많음
  - 라이브러리와 CLI 도구에서는 더 실용적일 수 있음
- ## 4. 게이트웨이 뒤에서 Strangler Pattern 적용
  - API 게이트웨이나 리버스 프록시가 있으면 특정 엔드포인트만 새 Rust 서비스로 라우팅하고 나머지는 Go에 남길 수 있음
  - 인증, 검색, 결제처럼 하나의 경계 지어진 컨텍스트가 마이그레이션 단위로 적합할 때 특히 잘 맞음
  - 이 패턴은 새 서비스가 기존 서비스 주변에서 자라 결국 완전히 대체한다는 의미로 [“strangler fig”](https://martinfowler.com/bliki/StranglerFigApplication.html)라고 불림

### 실전 마이그레이션 팁
- **명확한 경계가 있는 서비스**부터 시작해야 하며, 가장 중심적이고 가장 많이 배포되는 서비스를 고르면 안 됨
- 나머지 시스템과의 계약이 잘 정의돼 있고 영향 반경이 작은 서비스를 선택해야 함
- ## 같은 API 계약 유지
  - Go 서비스가 REST API를 노출한다면 Rust 서비스도 같은 경로, 같은 JSON 형태, 같은 오류 래퍼를 유지해야 함
  - 클라이언트에는 마이그레이션이 보이지 않으며, 게이트웨이로 트래픽을 점진적으로 전환할 수 있음
- ## 관용구를 문자 그대로 옮기지 않기
  - `if err != nil { return err }`는 `?`가 됨
  - 요청당 goroutine 패턴은 실제로 필요할 때만 `tokio::spawn`으로 옮김
  - `axum`은 이미 요청을 동시에 처리함
  - 메서드 하나짜리 인터페이스는 대개 `Box&lt;dyn Trait&gt;`가 아니라 제네릭의 trait bound가 됨
- ## 컴파일러를 페어 프로그래머처럼 사용
  - Rust 컴파일러 오류는 대체로 품질이 좋고, 천천히 읽으면 거의 항상 올바른 답을 알려줌
  - 가장 오래 고생하는 팀원은 컴파일러를 협력자로 보지 않고 싸우는 사람들임
- ## 초기에 훈련에 투자
  - Rust 마이그레이션을 “옆에서” 배우며 진행하면 잘 끝나지 않는 경우가 많음
  - 워크숍, [온라인 코스](https://course.corrode.dev/), 실제 코드 기반 페어 세션처럼 학습 시간을 실제로 확보해야 함
  - 팀이 능숙해지면 선투자가 여러 배로 회수됨

### Go가 계속 적합한 영역
- 모든 것을 Rust로 옮길 필요는 없으며, Go가 특히 좋은 영역이 있음
- ## Kubernetes 네이티브 도구
  - 오퍼레이터, 컨트롤러, CRD 영역은 생태계가 압도적으로 Go 중심임
- ## CLI 유틸리티와 개발 도구
  - 빠른 컴파일, 쉬운 크로스 컴파일, 단순한 배포가 강점임
- ## 글루 서비스
  - 얇은 API 계층, 프록시, 형식 변환기에서는 Rust의 보일러플레이트 비율이 그만한 가치가 없을 수 있음
- ## 팀 속도가 절대적 정확성 보장보다 중요한 곳
  - 빠르게 움직여야 하는 영역에서는 Go가 계속 적합할 수 있음
  - Canonical VP of Engineering Jon Seager는 [Rust in Production](https://corrode.dev/podcast/s05e05-canonical)에서 Go가 네트워킹 서비스에 매우 좋은 선택이며, Canonical에는 Go가 많고 Juju도 거대한 Go 코드베이스라고 말함
  - 하이브리드 전략은 흔하며, 많은 팀은 “지루한” 서비스에는 Go를, 안정성과 성능이 추가 노력을 회수하는 서비스에는 Rust를 쓰는 다언어 백엔드로 귀결됨

### 기대할 수 있는 개선
- 수치는 워크로드에 따라 크게 달라지므로 약속이 아니라 대략적 가이드로 봐야 함
- Go에서 Rust로의 마이그레이션에서 관찰된 대략적인 개선 범위:
  - **CPU 사용량**: 20~60% 감소
    - Go가 이미 효율적이므로 Python에서 Rust로 옮길 때보다 극적이지 않음
    - GC 부재와 더 타이트한 루프에서 이득이 나옴
  - **메모리**: 30~50% 감소
    - 주로 GC 오버헤드가 없고 런타임이 더 작기 때문임
  - **P99 지연 시간**: 훨씬 더 일관적임
    - Rust 서비스는 Go 서비스에서 보이는 GC 유발 지터가 줄고 평평해지는 경향이 있음
    - Go의 저지연 GC 도입 이후 Go 쪽도 많이 개선됐지만, 높은 부하에서는 차이가 남아 있음
  - **프로덕션 장애**: 팀들이 가장 적극적으로 보고하는 개선 영역임
    - `go test -race`를 통과하고 프로덕션까지 도달하는 데이터 레이스, nil 역참조, 누락된 오류 경로 같은 버그 종류는 Rust에서 컴파일되지 않음
    - Rust 마이그레이션 뒤 온콜 교대가 대체로 매우 지루해짐
- InfluxData Staff Engineer Andrew Lamb는 [Rustacean Station: Rebuilding InfluxDB with Rust](https://rustacean-station.org/)에서 InfluxDB 재작성 뒤 충돌, 이상한 멀티스레드 레이스 조건, 이전에 시간을 많이 잡아먹던 문제를 추적할 필요가 없었다고 말함
- Go에서 Rust로 옮긴다고 Python에서 Rust로 옮길 때처럼 처리량이 10배 개선될 가능성은 낮음
- 실제 이점은 “어이없는 오류” 감소, 더 평평한 지연 시간 꼬리, 같은 언어로 임베디드 개발이나 시스템 프로그래밍 같은 다른 영역까지 확장할 수 있는 능력임

### 보충 주의사항
- Rust 타입 시스템이 모든 동기화 로직 버그를 없애지는 않지만, 동기화 없이 스레드 사이에서 공유할 수 없는 타입은 컴파일되지 않음
- “락을 깜빡했다”가 조용한 데이터 손상으로 이어지는 종류의 문제는 Rust 타입 시스템이 막을 수 있음
- Go `string`은 불변 바이트 시퀀스이며 관례적으로 UTF-8이지만 타입 수준에서 보장되지는 않음
- 가장 가까운 대응은 읽기 전용 뷰 기준으로 Go `string` ↔ Rust `&str`, 가변 버퍼 기준으로 Go `[]byte` ↔ Rust `Vec&lt;u8&gt;`임
- Rust `String`은 `&str`의 소유권 있는 확장 가능한 버전이며, 내용이 유효한 UTF-8이라는 추가 보장이 있음
- 자세한 내용은 [Strings, bytes, runes and characters in Go](https://go.dev/blog/strings)를 참고할 수 있음
- Go 1.18부터 제네릭 함수와 제네릭 타입은 가능하지만, 메서드 자체의 타입 매개변수는 도입되지 않았음
- Rust의 `(0..100).filter(|n| ...).collect()` 같은 반복자 체인은 Go 개발자에게 낯설 수 있지만, Rust에서도 `for` 루프를 쓸 수 있고 일회성 코드에서는 종종 올바른 선택임

### 결론
- Go에서 Rust로의 전환은 [Python](https://corrode.dev/learn/migration-guides/python-to-rust)이나 [TypeScript](https://corrode.dev/learn/migration-guides/typescript-to-rust)에서 Rust로 가는 전환과 다름
- Go 출신 개발자는 이미 정적 타입과 컴파일 언어의 장점을 알고 있으므로, 동적 타입이나 느린 런타임을 포기하는 전환이 아님
- 핵심 교환은 `nil`을 버리고 더 견고한 코드베이스, 더 적은 함정, 컴파일 시간에 더 많은 실수를 잡는 엄격한 컴파일러를 얻는 것임
- 대신 학습 곡선은 더 가파름
- [기반 서비스](https://corrode.dev/blog/foundational-software/)처럼 조직이 의존하고, 높은 가동 시간이 필요하며, 비즈니스에 중요한 서비스에서는 이 교환이 명확히 가치 있음
- 다른 서비스에서는 Go가 여전히 맞는 답일 수 있음
- 마이그레이션의 목적은 각 문제를 가장 잘 해결하는 언어에 배치하는 것임

## Comments



### Comment 58170

- Author: neo
- Created: 2026-05-25T08:19:56+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=48259808) 
- C/C++이나 Python에서 Rust로 옮기는 건 여러 이유로 이해되지만, **웹 백엔드**라면 Go가 잘 맞는 선택으로 보임  
  거의 Rust만 쓰지만, 마지막으로 Rust로 웹 서버 쪽 작업을 했을 때는 Go를 쓸 걸 그랬다고 느꼈음  
  원문은 Go의 오류 처리 문법이 장황하다고 짚는데, 맞는 지적임. Rust도 같은 문제가 있다가 오류면 오류 값을 반환하는 `?` 문법을 추가했음. Go의 오류 처리는 대부분 이걸 풀어 쓴 형태임  
  Rust에는 통일된 오류 타입이 없고, `io::Error`, `thiserror`, `anyhow` 같은 주요 오류 체계가 있어 호출 체인을 따라 위로 전달할 때 번거롭다  
  새 언어에서 빠졌다가 나중에 덧붙이기 어려운 것들이 있음. 상수 타입, 불리언 타입, 오류 타입, 다차원 배열 타입, 2/3/4 크기의 벡터·행렬 타입과 표준 연산 등이 그렇다. 초기에 표준화하지 않으면 같은 개념의 여러 표현을 맞추느라 시간을 많이 쓰게 됨  
  오류 처리를 제외하면 웹 개발에는 덜 영향이 있지만, 수치 계산·그래픽·모델링에서는 숫자 배열에 표준 연산을 적용해야 해서 큰 고통이 됨  
  Go가 웹 서비스에서 가진 장점은 두 가지임. 첫째는 원문이 말한 **고루틴**이고, 둘째는 원문이 많이 다루지 않은 라이브러리임. Go에는 웹 서비스에 필요한 대부분의 라이브러리가 있고, Google 내부에서도 쓰이는 것들이라 매우 혹독한 환경을 견뎌냈음. 반면 Rust crate들은 성숙도가 낮고 공식 품질 보증이 없는 경우가 많다
  - Rust보다 Go가 가진 가장 큰 장점은 **컴파일 속도**라고 봄  
    또 Rust는 Go와 비교해 여전히 많은 C/C++ 라이브러리에 의존해서, 교차 컴파일이나 재현 가능한 빌드, 정적 바이너리 생성이 문제 되기 쉽다  
    Go의 단점은 가비지 컬렉터가 너무 단순하다는 점임. 지연 시간 스파이크가 생기면 고통스러운 재작성 말고는 대응 수단이 별로 없음
  - Rust에 사실상 하나의 오류가 있는데, 바로 **Error trait**임  
    나열한 것들은 그걸 사용하는 흔한 방식일 뿐이고, `Box`만 써도 전혀 문제 없음. 이는 대체로 `anyhow::Error`가 하는 것과 비슷함
  - 한동안 Go를 꽤 좋아했지만, 최근 Swift와 Rust를 더 많이 쓰다 보니 **널 포인터 역참조**를 막아주지 않고 동시성 안전 보장도 없는 컴파일러는 조금 선사시대적으로 느껴짐  
    다만 표준 라이브러리 쪽에서는 Go가 Rust보다 훨씬 잘했다고 봄
  - 동의함. 초반에 이 글이 백엔드 서비스를 위한 것이라고 한 부분이 눈에 띄었음  
    Rust 언어를 좋아하고 임베디드 펌웨어와 PC 애플리케이션에 쓰지만, 웹 백엔드는 여전히 Python을 씀. Rust에는 Django나 Rails급 도구 묶음이 없기 때문임  
    Flask 비슷한 것은 있지만 Flask의 탄탄한 생태계는 없음. Go 경험은 적지만, 웹 백엔드라면 Rust보다 Go를 고를 것 같음. 이유는 라이브러리와 프레임워크 **생태계** 때문임  
    또 일반적으로 말하는 이유들 때문에 Async Rust를 그다지 좋아하지 않음. Rust 웹 생태계는 거의 전부 비동기 사용이 필수에 가까움
  - Rust에는 오류 체계가 세 개 있는 게 아니라 하나, 즉 **Error trait**이 있음  
    `io::Error`는 이를 구현한 여러 타입 중 하나일 뿐 특별하지 않음. `thiserror`로 정의한 오류도 이 trait을 구현함  
    `anyhow`는 함수가 뱉을 수 있는 오류 타입을 API 계약으로 자세히 쓰고 싶지 않을 때 “어떤 Error”라고 편하게 말하게 해줄 뿐임

- Rust는 Go보다 코드를 **결정론적**으로 만들기 쉬워서, 결정론적 시뮬레이션 테스트와 속성 기반 테스트가 필요할 때 매우 유용함  
  최근 Go로 Postgres-to-Iceberg 데이터 미러링 도구 [https://github.com/polynya-dev/pg2iceberg](<https://github.com/polynya-dev/pg2iceberg>)를 썼지만, Go 런타임과 싸우지 않고 결정론적 시뮬레이션 테스트를 하고 싶어서 Rust로 포팅했음  
  다만 해당 도메인이 그 정도 테스트를 정당화할 만큼 중요하지 않다면, 언제든 Rust보다 Go를 고르겠음  
  관련 글: [https://www.polarsignals.com/blog/posts/2024/05/28/mostly-ds...](<https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go>)

- 뻔하고 반복적으로 들릴 수 있지만, Rust에 대한 가장 큰 불만은 **패키지 관리** 상황이고, 이는 전적으로 개발자 사고방식의 결과라고 봄  
  Rust 쪽 사용성은 좋아함. 데이터 타입에 대한 함수형 접근은 아름답다. 그런데 지금 Rust 프로젝트와 Go 프로젝트를 나란히 작업 중인데 의존성 트리가 완전히 다른 짐승임  
  Go 프로젝트는 대부분 표준 라이브러리로 해결되지만, Rust 프로젝트는 `rusqlite`(sqlite), `clap`(CLI), `ratatui`(TUI), `tauri`(GUI)만 요청했는데도 의존성이 400개가 넘는 듯함. 특히 `tauri`가 압도적인 주범이고, 그걸 빼도 거의 100개라서 미쳤다고 느껴짐  
  의존성을 합리적으로 다루는 잘 관리된 Rust crate 대안이 있다면 훨씬 나을 텐데, 아직 찾지 못했음. 시스템에 shai hulud를 들이고 싶지 않을 뿐인데, Rust 웹 쪽 사람들은 그 면에서 `cargo`를 `npm`처럼 만들고 싶어 하는 것처럼 보임
  - 많은 Rust 라이브러리가 여러 crate로 나뉘어 있고, 이들이 모두 의존성 그래프에 들어간다는 점을 감안해야 함  
    그래서 의존성 수가 실제보다 커 보임. 별도 crate라도 유지보수자가 같고 같은 upstream Git 저장소의 일부인 경우가 많다  
    그래도 전반적인 느낌에는 동의함. Rust에는 0.x 버전에 반쯤 방치된 crate도 많고, 더 나은 대안이 없는 경우가 자주 있음
  - **표준 라이브러리**는 좋은 아이디어가 죽으러 가는 곳이라고 봄  
    그러고 나면 `httplib3` 다음에 `httplib4`가 나옴  
    다시 말해 Rust 접근을 훨씬 선호함. 표준 라이브러리에 의존하든 다른 의존성에 의존하든 나에게는 큰 차이가 없음. 어쨌든 의존성임  
    표준 라이브러리라서 품질이 더 좋거나 유지보수가 더 잘된다고 생각하는데, 이건 별개의 개념임  
    결국 전부 자원에 달렸음. 물론 표준 라이브러리가 더 많은 자원을 받을 수는 있지만, 반대로 비대해지고 유지보수 불가능해질 수도 있음
  - `rusqlite`, `clap`, `ratatui`, `tauri`에 해당하는 것들을 표준 라이브러리에 모두 갖춘 언어가 Java 정도를 빼고 존재하나 싶음  
    또 **Tauri 자체가 14개 crate**로 구성되어 있고, 각각이 빌드 트리에 나타난다는 점도 봐야 함  
    [https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml](<https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml>)  
    Ratatui도 6개임  
    [https://github.com/ratatui/ratatui/blob/main/Cargo.toml](<https://github.com/ratatui/ratatui/blob/main/Cargo.toml>)
  - 패키지 관리는 거의 모든 언어와 기술의 골칫거리임  
    아무도 이를 “해결”하지 못했고, 앞으로도 하나의 해법이 나오긴 어렵다고 봄  
    Go에서는 라이브러리 개발자가 **시맨틱 버전 관리**를 정확히 지키리라 믿어야 하고, 버전을 고정할 수 없음. 이건 개인적으로도 꽤 거슬리는 부분임  
    우회책은 몇 가지 있음. Git 커밋 해시처럼 SHA를 써서 유사 버전을 만들거나, 알려진 의존성 캐시인 vendoring을 쓰는 방식임. 다만 vendoring은 캐시 관리 문제를 동반함  
    주말에 Python 가상 환경을 써야 했는데 좋게 끝나지 않았고, 왜 Python에서 벗어났는지 다시 떠올랐음  
    Perl의 CPAN, Java의 Maven/Gradle, Ruby의 gems, Go의 dep/glide/vgo/modules, Rust의 Cargo, Node의 npm/yarn 등 모두 비슷한 문제가 있음  
    운영체제도 Redhat의 yum/rpm, Debian의 apt, Ubuntu의 snap 같은 식임. 특히 snap은 대체 왜 그런지 모르겠음
  - Go에 익숙하지 않은데, Go 표준 라이브러리에서 **Tauri에 해당하는 것**이 무엇인지 궁금함  
    사용 사례에서 프런트엔드는 계속 Go로 두고 백엔드만 Rust로 하는 게 말이 될 수도 있나 싶음

- 이 문서는 **마이그레이션 가이드**이면서 동시에 Rust 옹호 문서가 되려 해서 이상하게 느껴짐  
  결국 Rust와 Go 중 무엇을 쓸지 고민한다면 핵심은 거의 전적으로 “관리형 런타임을 원하는가”로 귀결됨. 한 세대의 Rust 프로그래머들은 관리형 런타임이 나쁘고, 없는 것이 중요한 기능이라고 스스로 설득해 왔음  
  하지만 이는 명백히 틀렸음. 관리형 런타임을 원하는 프로그래밍 영역이 원하지 않는 영역보다 더 많다  
  그렇다고 그런 경우에 모두 Go를 기본 선택해야 한다는 뜻은 아님. Rust를 선호할 주관적 이유도 많음. Go를 쓸 때는 `match`가 그립지만, `tokio`와 Async Rust는 그립지 않음  
  둘 다 문제 공간을 억지로 비틀 필요가 없는 거의 모든 경우에 정당한 선택임. 예컨대 Go로 Linux 커널 모듈을 작성하려는 건 이상한 선택이겠지만 말임  
  Rust 대 Go 싸움은 우리 분야의 이상하고 민망한 변방 같음. 업계의 큰 부분은 Python이나 Node로 전체 시스템을 잘 만들고 있고, 어느 정적 타입 컴파일 언어를 쓸지 다투는 괴짜들을 비웃고 있음. 진짜 질문은 Python 대 Rust/Go이지, Rust 대 Go가 아님
  - Node를 PureScript와 함께 쓰는 건 괜찮을 수도 있다고 봄  
    하지만 전반적으로 Rust와 Go 쪽은 **동적 타이핑**의 악에 맞서 힘을 합쳐야 함. 타입 힌트가 이제 모범 사례로 여겨진다면, 이는 사실상 결함이었다는 인정 아닌가 싶음  
    좋은 타입 힌트가 있어도 타입 추론보다 못함. 타입 추론은 타입 변경 시 많은 코드를 그대로 둘 수 있게 해주면서도 의도치 않은 타입 변경은 막아줌
  - Node 쪽은 정적 컴파일 타입을 원해서 **TypeScript**를 받아들였음  
    TS에 런타임이 좀 더 있었으면 함. Python에서 부러운 유일한 점은 HTTP 엔드포인트에서 JSON 스키마 검증을 아주 자연스럽게 할 수 있다는 것임  
    Zod로 통과해야 하는 절차는 계속 짜증의 원천이고, TS 팀이 교조적이라서 생긴 문제라고 봄

- LLM 글쓰기의 흔적이 점점 미묘해지고 있지만, 여전히 눈에 확 띔. 특히 **genuine**이라는 단어가 그렇다  
  “This is the area where Go genuinely shines, and it’s worth being precise about why”, “the lack of GC pauses is a genuine selling point”, “Humans are genuinely bad at reasoning about memory”, “There are cases where the borrow checker is genuinely too strict” 같은 식임  
  글 전체가 AI 생성이라고 보진 않고, AI 보조를 받은 것 같음. 그렇다면 작성자는 genuinely 잘 해낸 셈임  
  다른 사람들이 이를 언급하지 않는 걸 보면 내용 자체를 크게 해치진 않은 듯하지만, 이런 일이 점점 흔해지고 감지하기 어려워지는 게 이상하게 느껴짐
  - 동의하지만 왜 그런지는 잘 모르겠음. 무엇이 AI 생성처럼 들리게 하는지 정확히 알지는 못함  
    “Go is clearly working for a lot of people,” 정도까지 읽고 AI 보조를 의심하게 됐음. 물론 아닐 수도 있고, 나는 판별을 잘 못함  
    구체적인 단서라기보다 아이러니하게도 **느낌**에 가까움. 어떤 글이 AI 보조처럼 “들리면” 글 자체가 괜찮아도 곧바로 흥미를 잃게 됨  
    사람들이 자기 생각을 떠오르는 방식대로 직접 쓰는 데 더 편해졌으면 함
  - 완전히 주제에서 벗어나지만, `it's worth being precise about ...`는 **genuine 사용**보다 훨씬 강한 AI스러운 표현임
  - 글 전체가 AI 생성이라고 생각함. 작성자가 초안을 입력으로 주고 출력 일부를 고쳤을 수는 있음  
    예를 들어 이 문단을 보면 그렇다: “Go got generics in 1.18, and they’re useful, but the implementation has constraints (no methods with type parameters, GC shape stenciling, occasional surprising performance characteristics). Rust generics monomorphize, each instantiation produces specialized code with zero runtime cost. Combined with traits, this gives you real zero-cost abstractions.”  
    모든 문장이 무언가를 말하고, 모든 문장이 중요하며 자기 몫을 함. 이런 글은 블로그 글보다는 매우 전문적인 책이나 논문에서 기대할 만함  
    그래서 오히려 글을 더 읽기 어렵고 지루하게 만듦
  - 지난 1년 동안 LLM 글쓰기는 **표면**과 특히 **기반층**에 대해 말하는 경향이 유난히 강하다고 느꼈음  
    LLM 생성 텍스트가 진부한 표현으로 가득하지 않을 거라고 기대하지는 않음. 다만 우리 모두가 더 나은 편집 감각을 보여서 같은 목소리를 반복해서 읽지 않게 되길 바람

- 신규 프로젝트라면 얼마든지 Rust로 쓰면 됨  
  하지만 기존 코드가 있고, 작동하며 수익을 내는 시스템이라면 다시 써야 할 부분만 원래 언어로 고쳐서 계속 가는 게 맞음  
  아는 언어와 신뢰할 수 있는 팀으로 시스템을 작고 측정 가능한 방식으로 개선하라. 그 외는 낭비적인 **종교 논쟁**임
  - 팀이 C#/Java/Go 등으로 성공적으로 출시했고 편하게 쓰고 있다면, **Rust를 쓸 이유**가 보이지 않음

- 벤치마크를 돌리기 전에도 Rust를 좋아했지만, 대부분의 LLM이 Rust와 Go를 작성하는 효율 차이가 생각보다 훨씬 컸음. 특히 초기 환경 문제를 고칠 수 있는 에이전트형 하네스에서는 더 그랬음  
  그걸 보고 꽤 강한 Rust 전도사가 됐음. 기존 코드베이스에서 호출할 **배치 처리 도구**를 Rust로 작성해 좋은 성과를 냈지만, 전체 프로덕션 마이그레이션은 아직 시도하지 않았음  
  글에서 말한 Go의 문제들, 특히 `nil` 처리 관련 문제는 Codex로 철저히 코드 리뷰하면 점점 해결되고 있다고 봄. 애초에 문제가 없으면 더 좋지만, 설계와 구현에 들인 노력만큼 리뷰와 이해에도 노력하는 개발자에게는 이런 보안 버그가 선택 사항이 되어가고 있음  
  언어 데이터는 [https://gertlabs.com/rankings?mode=agentic_coding](<https://gertlabs.com/rankings?mode=agentic_coding>)에 있음
  - 자세한 컴파일러 오류와 강한 타입 시스템 덕분에 에이전트가 **수정 → 컴파일 → 수정** 반복을 다루기 쉬움  
    Rust는 사용자를 강하게 정해진 궤도 위로 올려놓음. Codex는 항상 뭔가 컴파일되게 만들어냄  
    단점은 때로는 관용적인 접근이 불가능할 때 실패해야 할 수도 있는데, 대신 컴파일되고 요청을 만족하는 멍청한 구현을 만들어낼 수 있다는 점임
  - LLM 관점에서 Rust의 약점은 **컴파일 시간**임  
    LLM은 사람보다 코드를 빠르게 쓰기 때문에, 상대적으로 컴파일을 기다리는 시간이 더 커짐. 10만 줄 이상 같은 어느 정도 규모 있는 프로젝트에서는 Rust의 약 10배 느린 컴파일이 병목으로 나타나기 시작함  
    핵심 인프라를 작성한다면 그 비용을 치를 만하지만, 인터넷에 공개되지 않은 내부 서비스를 만든다면 개발 속도가 더 큰 관심사일 수 있음  
    느린 컴파일은 사람의 개발 속도에도 영향을 준다고 보지만, 이상하게도 개발자들은 이를 정량화하려는 경우가 매우 드묾

- 장황함이 주된 걸림돌이라면, Go 1.28에 들어올 예정인 이것이 크게 줄여줄 수 있음  
  [https://github.com/golang/go/issues/12854#issue-110104883](<https://github.com/golang/go/issues/12854#issue-110104883>)

- “조직이 의존하고, 높은 가동 시간이 필요하며, 사업에 치명적인 서비스”라는 표현이 재미있음  
  그 **Rust 서비스**가 Kubernetes 위에서 돌 때는 특히 그렇다

- 이미 Rust를 쓰고 Go 경험은 없어서, 이 글이 나에게 아주 맞는 글은 아닐 수 있음  
  다만 한 가지 걸리는 점이 있음. Rust에서 데이터 경합이 “컴파일 타임에 잡힌다”고 말하는 건 적어도 조금은 과장처럼 느껴짐  
  이 표현은 Rust가 상호 잠금 기아 같은 것들이나 다른 동시성 문제까지 처리할 수 있다는 인상을 줄 수 있음. 실제로는 그렇지 않음  
  **데이터 경합**이 좁은 범위를 가진 형식적 용어라는 건 알지만, 그래도 더 명확하게 쓸 수 있다고 봄
