Go 개발자가 Rust를 만나면 벌어지는 일
요즘 백엔드 진영에서 흥미로운 흐름이 하나 보이는데요. Go로 잘 돌아가던 서비스를 Rust로 옮기는 팀들이 늘고 있어요. Discord, Cloudflare, 그리고 최근에는 Microsoft까지요. 그런데 막상 마이그레이션을 해본 사람들 얘기를 들어보면, "문법 배우는 건 일주일이면 되는데 사고방식 바꾸는 데 6개월 걸렸다"는 말이 공통적으로 나와요. corrode라는 Rust 컨설팅 회사에서 정리한 Go-to-Rust 마이그레이션 가이드가 바로 이 지점을 정확하게 짚고 있어서 한번 풀어볼게요.
둘 다 시스템 프로그래밍 언어이고 동시성을 중요하게 다룬다는 점에서 비슷해 보이지만, 사실 철학이 정반대거든요. Go는 "단순함과 빠른 컴파일"을 위해 많은 걸 포기했고, Rust는 "안전성과 성능"을 위해 학습 곡선을 포기했어요.
가장 큰 충격: 에러 처리
Go를 쓰던 분이라면 if err != nil 패턴이 손에 배어 있을 거예요. 함수 호출하고, 에러 체크하고, 또 다음 호출하고. 코드의 30%가 에러 체크라는 농담이 괜히 나온 게 아니죠. Rust로 넘어가면 이 부분이 완전히 달라져요.
Rust는 Result<T, E>라는 타입으로 에러를 표현하는데요. 이게 뭐냐면, 함수가 성공했을 때의 값(T)이나 실패했을 때의 에러(E) 중 하나를 반드시 담는 상자라고 생각하면 돼요. 그리고 ? 연산자라는 게 있어서, 에러가 나면 자동으로 호출자에게 전파시키고 성공하면 값을 꺼내줘요. Go에서 5줄 걸리던 에러 전파가 한 글자로 끝나는 거예요.
더 중요한 차이는 "에러를 무시할 수 없다"는 거예요. Go에서는 _ = someFunc()처럼 에러를 슬쩍 버릴 수 있는데, Rust에서는 컴파일러가 "너 이 Result 안 썼는데 진짜 괜찮아?"라고 경고를 띄워요. 실수로 에러를 흘려보내는 일이 구조적으로 막혀 있는 셈이죠.
동시성 모델의 근본적 차이
Go의 매력은 goroutine과 channel이잖아요. go func() 한 줄로 가벼운 스레드를 띄울 수 있고, 채널로 데이터를 주고받으면 끝. 정말 직관적이에요. 그런데 이 편함의 대가로 데이터 레이스(여러 스레드가 같은 데이터를 동시에 건드려서 생기는 버그)는 런타임에 가야 발견되는 경우가 많아요. Go의 race detector를 돌려야 잡히는 거죠.
Rust는 이걸 컴파일 타임에 잡아요. 소유권(ownership)과 빌림(borrowing)이라는 개념이 있는데, 쉽게 말하면 "어떤 데이터를 한 시점에 누가 갖고 있느냐"를 컴파일러가 추적해요. 두 스레드가 같은 가변 데이터를 동시에 만지려고 하면 컴파일 자체가 안 돼요. 처음에는 이 borrow checker라는 게 진짜 짜증나거든요. 분명히 안전한 코드 같은데 컴파일러가 계속 거부해요. 근데 익숙해지면 "아, 내가 위험한 짓을 하려고 했구나" 하고 깨닫게 돼요.
비동기도 다른데요. Go는 goroutine이 언어 런타임 차원에서 스케줄링되지만, Rust의 async/await는 명시적인 런타임(tokio, async-std 같은 거)을 골라서 써야 해요. 자유도가 높은 대신 초기 설정이 복잡한 트레이드오프죠.
인터페이스 vs 트레이트
Go의 인터페이스는 "덕 타이핑"이에요. Reader 인터페이스가 있으면, Read 메서드만 구현하면 자동으로 그 인터페이스를 만족하는 거예요. 별도로 선언할 필요가 없죠.
Rust의 트레이트(trait)는 좀 더 명시적이에요. 어떤 타입이 어떤 트레이트를 구현한다고 impl Trait for Type 형태로 선언해줘야 해요. 대신 제네릭과 결합하면 훨씬 강력해져요. 컴파일 타임에 모든 타입이 결정되니까 런타임 오버헤드가 없는 "zero-cost abstraction"이 가능하거든요.
업계 흐름에서 보면
Discord가 Go로 짠 Read States 서비스를 Rust로 옮긴 사례가 유명한데요. GC(가비지 컬렉션, 메모리 자동 정리) 때문에 생기는 지연 스파이크를 없애려고 갔어요. Cloudflare도 Pingora라는 프록시를 nginx에서 Rust로 바꿔서 메모리를 67% 줄였고요. 최근에는 Microsoft가 Windows 커널 일부를 Rust로 다시 쓰고 있죠.
반대로 모든 걸 Rust로 옮길 필요는 없어요. Go는 컴파일이 빠르고, 표준 라이브러리만으로 웹 서버를 뚝딱 만들 수 있고, 팀에 새 사람 들어와도 일주일이면 코드를 짤 수 있어요. CRUD API나 마이크로서비스에는 여전히 Go가 좋은 선택이에요. Rust는 정말 성능이 중요하거나, 메모리 안전성이 critical하거나, GC 일시정지가 문제되는 영역에서 빛을 발하죠.
한국 개발자에게
국내에서도 토스, 당근, 카카오 같은 곳에서 Rust 도입 사례가 조금씩 나오고 있어요. 당장 회사에서 Rust로 갈아탈 일은 없더라도, 사이드 프로젝트로 한번 만져보길 추천해요. Rust를 배우면 메모리 관리, 동시성, 타입 시스템에 대한 이해가 깊어져서 다른 언어 쓸 때도 더 좋은 코드를 짜게 되거든요. 마치 C를 배우면 다른 언어가 내부적으로 뭘 하는지 보이는 것처럼요.
특히 마이그레이션을 고민한다면, 전체 서비스를 한 번에 옮기지 말고 가장 hot path(성능이 critical한 부분)부터 Rust 라이브러리로 떼어내서 FFI나 별도 서비스로 붙이는 점진적 접근이 현실적이에요.
마무리
Go가 "덜 생각하게 해주는 언어"라면, Rust는 "더 생각하게 만드는 언어"예요. 두 언어 다 좋은 도구이고, 결국 풀고 싶은 문제에 따라 고르면 돼요. 여러분은 어떤 상황에서 Go 대신 Rust를 고려해볼 만하다고 생각하세요? 혹시 실제로 마이그레이션 경험이 있다면 어떤 부분이 가장 힘들었는지도 궁금하네요.
🔗 출처: Hacker News
"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"
실제 수강생 후기- 비전공자도 6개월이면 첫 수익
- 20년 경력 개발자 직강
- 자동화 프로그램 + 소스코드 제공