러스트 언어 입문 시리즈 - 1편: 시작하기 전에 알아두면 좋은 것들

프로그래밍 언어 러스트(Rust)를 배우려는 분들을 위한 첫 번째 글입니다. 이 시리즈는 전반적으로 러스트를 처음 접하는 사람들을 대상으로, 기초부터 차근차근 설명해나갈 예정입니다. 특히 이미 C++에 익숙한 분들을 위해 C++과의 차이점을 통해 러스트가 어떤 특성을 갖고 있는지 함께 살펴보겠습니다.

왜 러스트인가?

C++로 이미 다양한 프로젝트를 진행해본 분이라면, 메모리 관리나 복잡한 템플릿 문법, 그리고 때때로 발생하는 미묘한 정의되지 않은 동작(Undefined Behavior)에 익숙하실 겁니다. 러스트는 이러한 부분을 조금 더 안전하고 명시적으로 처리하고자 하는 철학을 가지고 탄생한 언어입니다.

  • 메모리 안전성: 러스트는 소유권(Ownership)과 빌림(Borrowing), 라이프타임(Lifetime) 시스템을 통해 메모리 안전성을 보장하려 합니다. C++에서 스마트 포인터, RAII 패턴을 애용했다면, 러스트는 이 개념들을 언어 차원에서 강제하고 좀 더 명확하고 일관되게 사용하는 느낌이라 할 수 있습니다.
  • 성능: 러스트는 C++처럼 네이티브 코드를 생성하며, 높은 성능을 목표로 합니다. 즉, "GC(가비지 컬렉션) 없는 언어"라는 점에서 C++와 비슷한 성격을 갖추고 있습니다.
  • 친근한 컴파일러: 초기 러스트를 접하면 컴파일 에러 메시지가 너무 길게 느껴질 수 있지만, 컴파일러는 매우 친절하게 문제를 지적하고 해결책에 대한 힌트를 줍니다. C++ 컴파일러보다 훨씬 상세하고 구체적인 에러 안내를 받을 수 있어, 러스트 학습 과정에서 큰 도움이 됩니다.

"Hello, world!" 예제로 살펴보기

일단 러스트를 설치했다면(설치 방법은 뒤의 링크 참조), 가장 기본적인 예제로 시작해봅시다. C++에서의 "Hello, world!"와 비교해봅시다.

C++ 예제 (예시):

#include <iostream>
int main() {
    std::cout << "Hello, world!\n";
    return 0;
}

러스트 예제:

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

러스트에서는 fn main()으로 시작하고, println! 매크로를 사용하여 콘솔에 출력을 합니다. #include나 using namespace std; 같은 헤더 관련 문법이 없고, 문법이 깔끔하게 정리된 느낌을 받으실 겁니다.

C++에서 std::cout를 사용할 때 필요했던 #include <iostream> 같은 부분이 러스트에선 별도로 등장하지 않습니다. 러스트 컴파일러와 Cargo(러스트 패키지 매니저)를 사용하면 필요한 라이브러리는 주로 Cargo.toml이라는 설정 파일을 통해 관리하게 됩니다. 자세한 사항은 다음 글에서 더욱 자세히 다뤄보겠습니다.

러스트에서의 안전한 메모리 관리 개념 체험하기

C++에서 메모리 관리는 다양한 방식으로 이뤄집니다. new, delete, 스마트 포인터(std::unique_ptr, std::shared_ptr) 등 다양한 선택지가 있죠. 반면 러스트에서는 소유권 규칙과 빌림 개념을 언어 차원에서 강제합니다.

아주 간단한 예시를 들어봅시다.

fn main() {
    let s = String::from("Hello");
    let s2 = s; // s의 소유권이 s2에게 이동(Ownership Move)
    // println!("{}", s); // 이 줄은 에러가 납니다. s는 더 이상 유효하지 않음
    println!("{}", s2);
}

C++ 경험자로서 이 상황을 비유하자면, s가 std::string을 가리키던 메모리를 s2에게 "소유권 이전(move semantics)"을 통해 넘긴 것이나 마찬가지입니다. C++11 이후의 move semantics와 유사한 개념이지만, 러스트에서는 이 개념이 더 명확하고 강제적으로 적용됩니다. 이로 인해 "유효하지 않은 포인터 접근"과 같은 문제를 언어 수준에서 방지할 수 있습니다.

C++와의 비교를 통한 러스트 이해

  • 템플릿 vs. 제네릭(Generic): C++ 템플릿은 컴파일 시 코드 확장을 통해 제네릭을 구현하지만, 때로는 복잡한 SFINAE나 개념(Concept) 활용, 템플릿 인스턴시에 따른 빌드 속도 문제 등이 있습니다. 러스트의 제네릭은 trait 바운드를 통해 타입에 대한 약속을 강제하고, 좀 더 명확한 형태의 제네릭 프로그래밍을 지원합니다.
  • 객체지향 vs. 트레이트(Interfaces): C++에서는 클래스 기반 객체지향과 가상 함수 테이블(vtable)을 이용합니다. 러스트는 클래스가 없고, trait을 통해 메서드 집합을 인터페이스처럼 정의합니다. 이로써 복잡한 상속 구조 없이도 타입 간 기능 공유와 다형성을 구현할 수 있습니다.
  • 빌드 시스템: C++에는 CMake, Meson, Bazel 등 다양한 빌드 시스템이 존재하고, 프로젝트별로 선택해야 하는 경우가 많습니다. 러스트는 cargo라는 패키지 매니저 겸 빌드 시스템이 기본 제공됩니다. cargo build, cargo run, cargo test 등을 통해 일관되고 간결한 빌드/실행 환경을 사용할 수 있습니다.

앞으로의 학습 방향

이 글에서는 러스트가 왜 각광받고 있는지, 그리고 C++와 비교했을 때 어떤 철학적·구조적 특징이 있는지 간단히 살펴봤습니다. 다음 글에서는 러스트의 패키지 매니저 Cargo 사용법, 기본적인 변수 선언과 함수 정의, 제어문 등에 대해 알아보겠습니다.

익숙한 C++ 관점을 잠시 내려놓고 러스트식 사고법에 익숙해진다면, 메모리 안전성을 얻는 대가로 감수했던 복잡도를 러스트 컴파일러가 훨씬 깔끔하게 관리해줄 것입니다.

유용한 링크와 리소스

반응형