Go vs Rust? Go를 선택하세요.
이 글은 꽤 오래전에 썼습니다. 그 사이 일부 내용에 대한 제 생각이 바뀌었습니다.
장단점에 대해 좀 더 균형 잡힌 시각을 원하신다면 Shuttle 🚀과 협업해 작성한 Go vs Rust 비교 글을 대신 읽어보시길 권합니다.
“Rust와 Go 중 무엇을 선택해야 할까요?”라는 질문을 정말 자주 받습니다. 두 언어 모두 같은 사용자층을 두고 경쟁하는 것처럼 보이고, 둘 다 시스템 프로그래밍 언어처럼 보이니 분명한 승자가 있어야 할 것 같죠?
Go: 실용적이고, 실무적이며, 담백하다

Go가 우아한 언어라고 생각하지는 않습니다. Go의 가장 큰 특징은 단순함 쉬움입니다. Go는 사실 시스템 프로그래밍 언어라고 하기도 어렵습니다. 백엔드 인프라를 위한 마이크로서비스나 툴을 만드는 데는 훌륭하지만, 커널이나 메모리 할당자를 Go로 작성하고 싶지는 않습니다.
하지만 Go로는 일을 빠르게 끝낼 수 있습니다.
Go는 제가 다뤄본 언어 중 가장 생산성이 높은 언어 중 하나입니다. Go의 모토는 오늘 당장 실제 문제를 해결하자는 것입니다.
Rust의 강력한 보장은 대가가 따른다

반면 Rust는 어렵습니다. 어느 정도 생산성을 갖추기까지 수개월이 걸렸습니다. 이점을 체감하려면 상당한 시간을 투자해야 합니다. Rust는 이미 강력한 언어이고 날마다 더 강력해지고 있습니다. 저에게는 더 안전한 C라기보다 실용적인 Haskell처럼 느껴집니다.
오해는 마세요. 저는 Rust를 좋아합니다. Rust 덕분에 더 나은 프로그래머가 될 수 있었습니다. 분명 배워볼 만한 멋진 언어입니다. 다만 다음 대형 프로젝트에 적합한 선택인지는 따져봐야 합니다.
핵심은 이렇습니다. Rust를 선택한다면 보통 그 언어가 제공하는 보장이 필요하기 때문입니다.
Null포인터, 경쟁 조건, 온갖 저수준 위협으로부터의 안전성.- 예측 가능한 런타임 동작(비용 없는 추상화와 가비지 컬렉터 없음).
- 하드웨어에 대한 (거의) 완전한 제어(메모리 레이아웃, 프로세서 기능).
- 다른 언어와의 매끄러운 상호 운용성.
이런 기능 중 어느 하나도 꼭 필요하지 않다면, 다음 프로젝트에 Rust는 그다지 좋은 선택이 아닐 수 있습니다. 이러한 보장은 대가가 따르기 때문입니다. 바로 학습에 드는 시간입니다. 잘못된 습관을 버리고 새로운 개념을 배워야 합니다. 처음에는 빌림 검사기와 씨름할 가능성이 큽니다.
사례 연구: 시행 나눗셈으로 소수 판별하기
어떤 수가 소수인지 확인하고 싶다고 해보겠습니다. 가장 쉬운 방법은 그 수보다 작은 모든 자연수로 나누어 떨어지는지 확인하는 것입니다. 나누어 떨어지지 않는다면 소수를 찾은 것입니다! 이 방식을 시행 나눗셈이라고 합니다.
Go에서는 이렇게 할 수 있습니다(Rosetta Code 제공):
func IsPrime(n int) bool {
if n < 0 {
n = -n
}
switch {
case n < 2:
return false
default:
for i := 2; i < n; i++ {
if n%i == 0 {
return false
}
}
}
return true
}그리고 Rust에서는 같은 기능을 이렇게 구현합니다.
pub fn is_prime(n: u64) -> bool {
match n {
0...1 => false,
_ => {
for d in 2..n {
if n % d == 0 {
return false;
}
}
true
}
}
}언뜻 보면 두 해법이 꽤 비슷해 보입니다. 하지만 자세히 보면 몇 가지 차이점이 눈에 띕니다.
- Go에서는 단순한 switch-case 문을 사용합니다. Rust에서는 훨씬 더 강력한
match문을 사용합니다. - Go에서는 2부터 n까지 숫자를 순회하기 위해 단순한 for 루프를 사용합니다. Rust에서는 범위 표현식(
2..n)을 사용합니다. - Go에서는 두 개의
return문을 사용하지만, Rust에서는 하나의return표현식을 사용합니다. 일반적으로 Rust에서는 대부분의 요소가 표현식이며, 변수에 할당하거나 반환할 수 있습니다. 표현식에 대한 자세한 내용은 여기에서 확인하세요.
많은 영역에서 Rust는 Go보다 함수형에 가깝습니다. 위 코드는 Range에 구현된 any 메서드를 사용해 다시 작성할 수도 있습니다.
fn is_prime(n: u64) -> bool {
match n {
0...1 => false,
_ => !(2..n).any(|d| n % d == 0),
}
}처음에는 조금 낯설게 느껴질 수 있지만, 조금만 지나면 자연스럽게 익숙해집니다.
물론 이는 간단한 예시에 불과합니다. 두 언어에 대한 감을 더 잡고 싶다면 Rosetta Code에서 코드를 둘러보시길 권합니다.
사례 연구: 텍스트 파일에서 중복 단어 찾기
시각적인 방식을 선호한다면, 두 언어를 비교하기 위해 Go와 Rust로 간단한 동시성 프로그램을 작성한 영상입니다.
Go에서 더 마음에 드는 점
- 빠른 컴파일 시간
- 실용적인 문제 해결 접근법
- 전형적인 DevOps 작업을 위한 훌륭한 생태계
- 필요한 것이 다 들어 있는 표준 라이브러리
- IDE 지원
- 간단한 에러 처리
- 마스코트 😉
Rust에서 더 마음에 드는 점
- 안전성: null 포인터 없음, 데이터 경쟁 없음 …
- 세밀한 시스템 제어
- 놀라운 런타임 속도(C/C++에 필적)
- 비용 없는 추상화
- 멋지고 열린 커뮤니티
- cargo를 통한 간편한 패키지 관리
- 트레이트 형태의 제네릭 지원
- C 상호 운용성 및 FFI
결론
99%의 경우 Go면 “충분히 좋습니다”. 그리고 나머지 1%에서 Go가 부족하다는 것은 여러분 스스로 알게 될 것입니다. 그때 Rust를 살펴보세요. 두 언어는 서로를 꽤 잘 보완합니다. 실전 Rust 컨설팅에 관심이 있다면, 제 캘린더에서 날짜를 선택해 어떻게 도와드릴 수 있을지 이야기해 보시죠.
글을 무작위로 읽기