Go vs Rust? Choose Go.

Matthias Endler

Go vs Rust? Go를 선택하라.

원문은 Matthias Endler님이 에 게재했습니다. 이 블로그 구독하기

Gopher는 Gopherize.me로 제작. 기어는 Freepik 제공.
출처: Gopher는 Gopherize.me에서 디자인. 기어는 Freepik에서 디자인.

이 글은 오래전에 작성한 글입니다. 그 사이 일부 내용에 대한 생각이 바뀌었습니다.

장단점을 보다 균형 잡힌 시각으로 보고 싶다면, Shuttle과 협업해 새로 작성한 Go vs Rust 비교 글을 대신 읽어보시길 권합니다. 🚀

Rust vs Go: A Hands-On Comparison

“Rust와 Go 중 뭘 선택해야 할까요?”라는 질문을 자주 받습니다. 두 언어 모두 같은 사용자층을 두고 경쟁하는 것처럼 보이고, 둘 다 시스템 프로그래밍 언어처럼 보이니 당연히 명확한 승자가 있어야 할 것 같죠?

Go: 실용적이고, 실무적이며, 단순하다

시간에 따른 Golang 학습 곡선, 일직선.
시간에 따른 Golang 학습 곡선, 일직선.

Go가 우아한 언어라고는 생각하지 않습니다. Go의 가장 큰 장점은 단순함 쉬움입니다. Go는 사실 시스템 프로그래밍 언어라고 하기도 어렵습니다. 백엔드 인프라를 위한 마이크로서비스나 툴을 만드는 데는 훌륭하지만, 커널이나 메모리 할당자를 Go로 작성하고 싶지는 않습니다.

하지만 Go로는 일을 빠르게 해낼 수 있습니다 — 빠르게.
Go는 제가 써본 언어 중 가장 생산성이 높은 언어 중 하나입니다. 핵심은 바로 이것입니다: 오늘 당장 현실의 문제를 해결하라.

Rust의 강력한 보장은 대가를 치러야 한다

시간에 따른 Rust 학습 곡선, 울퉁불퉁한 여정.
시간에 따른 Rust 학습 곡선, 울퉁불퉁한 여정.

반면 Rust는 어렵습니다. 어느 정도 생산적으로 다룰 수 있게 되기까지 수개월이 걸렸습니다. 이점을 체감하려면 상당한 시간을 투자해야 합니다. Rust는 이미 강력한 언어이며 날이 갈수록 더 강력해지고 있습니다. 제게는 더 안전한 C라기보다 실용적인 Haskell에 가깝게 느껴집니다.

오해는 마세요. 저는 Rust를 사랑합니다. Rust 덕분에 더 나은 프로그래머가 될 수 있었죠. 분명 배워볼 만한 멋진 언어입니다. 다만 다음 대형 프로젝트에 적합한 선택인지는 별개의 문제입니다.

핵심은 이겁니다. Rust를 선택한다면 보통 이 언어가 제공하는 보장이 필요하기 때문입니다.

  • Null 포인터, 레이스 컨디션, 온갖 로우레벨 위협으로부터의 안전성.
  • 예측 가능한 런타임 동작(제로 코스트 추상화와 가비지 컬렉터 없음).
  • 하드웨어에 대한 (거의) 완전한 제어(메모리 레이아웃, 프로세서 기능).
  • 다른 언어와의 매끄러운 상호 운용성.

이런 기능 중 어느 하나도 꼭 필요하지 않다면, Rust는 다음 프로젝트에 그다지 좋은 선택이 아닐 수 있습니다. 이러한 보장에는 대가가 따르기 때문입니다. 바로 적응 기간입니다. 나쁜 습관을 버리고 새로운 개념을 배워야 합니다. 처음에는 borrow checker와 씨름할 가능성이 높습니다.

사례 연구: 시도 나눗셈으로 소수 판별하기

어떤 수가 소수인지 확인하고 싶다고 해보겠습니다. 가장 쉬운 방법은 그 수보다 작은 자연수 중 나누어떨어지는 수가 있는지 확인하는 것입니다(나머지 없이). 하나도 없다면 그 수는 소수입니다! 이 방식을 시도 나눗셈(trial division)이라고 합니다.

Golang으로는 이렇게 할 수 있습니다(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는 Golang보다 더 함수형에 가깝습니다. 위 코드를 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를 통한 간편한 패키지 관리
  • trait 형태의 제네릭 지원
  • C 상호 운용성과 FFI

결론

99%의 경우 Go면 “충분히” 좋고, 나머지 1%에서 충분하지 않을 때는 여러분도 알게 될 겁니다. 그때 Rust를 살펴보세요. 두 언어는 서로를 꽤 잘 보완합니다. 실전 Rust 컨설팅에 관심이 있다면 캘린더에서 날짜를 선택해 어떻게 도와드릴 수 있을지 이야기 나눠 봅시다.

결국, Rust와 Go는 사실 경쟁 관계가 아닙니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글