Hello, Cargo!

Hello, Cargo!

Cargo는 Rust의 빌드 시스템이자 패키지 매니저입니다. 대부분의 Rustaceans은 이 도구로 Rust 프로젝트를 관리하는데요, Cargo가 코드 빌드, 코드가 의존하는 라이브러리 내려받기, 그 라이브러리들의 빌드 같은 많은 작업을 대신 처리해주기 때문이에요. (코드가 필요로 하는 라이브러리를 *의존성(dependency)*이라고 부릅니다.)

지금까지 작성한 것 같은 가장 단순한 Rust 프로그램은 의존성이 없어요. "Hello, world!" 프로젝트를 Cargo로 빌드했다면 Cargo의 코드 빌드를 담당하는 부분만 사용했을 거예요. 더 복잡한 Rust 프로그램을 작성하면서 의존성을 추가하게 되는데, Cargo로 프로젝트를 시작하면 의존성 추가가 훨씬 쉬워집니다.

압도적 다수의 Rust 프로젝트가 Cargo를 사용하기 때문에, 이 책의 나머지 부분에서도 당신도 Cargo를 쓴다고 가정할게요. "Installation" 섹션에서 설명한 공식 설치 프로그램으로 Rust를 설치했다면 Cargo는 Rust와 함께 설치됩니다. 다른 방법으로 Rust를 설치했다면 터미널에 다음을 입력해 Cargo가 설치됐는지 확인하세요.

$ cargo --version

버전 번호가 보인다면 잘 설치된 거예요! command not found 같은 오류가 보인다면 자신의 설치 방법 문서를 확인해 Cargo를 별도로 설치하는 방법을 알아보세요.

출처: The Rust Book

본문

Cargo로 프로젝트 만들기

Cargo를 사용해 새 프로젝트를 만들고, 원래 "Hello, world!" 프로젝트와 어떻게 다른지 살펴볼게요. projects 디렉터리(또는 코드를 저장하기로 한 곳)로 돌아간 뒤, 운영체제에 관계없이 다음을 실행하세요.

$ cargo new hello_cargo
$ cd hello_cargo

첫 번째 명령은 hello_cargo라는 새 디렉터리와 프로젝트를 만듭니다. 프로젝트 이름을 hello_cargo로 지었고, Cargo는 같은 이름의 디렉터리 안에 파일들을 만듭니다.

hello_cargo 디렉터리로 들어가 파일 목록을 확인해보세요. Cargo가 두 개의 파일과 한 개의 디렉터리를 만들어줬다는 걸 볼 수 있을 거예요. 하나는 Cargo.toml 파일이고, 하나는 main.rs 파일이 들어 있는 src 디렉터리입니다.

Cargo는 또한 .gitignore 파일과 함께 새 Git 리포지토리도 초기화합니다. 기존 Git 리포지토리 안에서 cargo new를 실행하면 Git 파일이 생성되지 않으며, 이 동작은 cargo new --vcs=git으로 재정의할 수 있습니다.

참고: Git은 흔한 버전 관리 시스템입니다. cargo new가 다른 버전 관리 시스템이나 아무 버전 관리 시스템도 사용하지 않도록 --vcs 플래그로 바꿀 수 있어요. 사용 가능한 옵션은 cargo new --help를 실행해 확인하세요.

원하는 텍스트 에디터로 Cargo.toml을 열어보세요. Listing 1-2의 코드와 비슷해 보일 거예요.

Filename: Cargo.toml

[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2024"

[dependencies]

이 파일은 TOML (Tom's Obvious, Minimal Language) 형식인데요, 이게 Cargo의 구성 형식입니다.

첫 번째 줄인 [package]는 섹션 제목으로, 뒤따르는 문장들이 패키지를 구성하는 것임을 나타냅니다. 이 파일에 정보를 더 추가하면서 다른 섹션들도 추가하게 될 거예요.

다음 세 줄은 Cargo가 프로그램을 컴파일하는 데 필요한 구성 정보, 즉 이름, 버전, 그리고 사용할 Rust의 버전(edition)을 설정합니다. edition 키에 대해서는 Appendix E에서 다룰게요.

마지막 줄인 [dependencies]는 프로젝트의 의존성을 나열할 섹션의 시작입니다. Rust에서 코드 패키지는 crate라고 일컬어요. 이 프로젝트에는 다른 crate가 필요 없지만, Chapter 2의 첫 프로젝트에서는 필요할 것이므로 그때 이 dependencies 섹션을 사용하게 될 겁니다.

이제 src/main.rs를 열어 확인해보세요.

Filename: src/main.rs

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

Cargo가 Listing 1-1에서 우리가 작성한 것과 같은 "Hello, world!" 프로그램을 만들어줬어요! 지금까지 우리 프로젝트와 Cargo가 생성한 프로젝트의 차이는, Cargo가 코드를 src 디렉터리에 두고, 최상위 디렉터리에 Cargo.toml 구성 파일을 두었다는 점뿐입니다.

Cargo는 소스 파일이 src 디렉터리 안에 있기를 기대합니다. 최상위 프로젝트 디렉터리는 README 파일, 라이선스 정보, 구성 파일, 그리고 코드와 무관한 다른 것들을 위한 곳입니다. Cargo를 쓰면 프로젝트를 체계적으로 정리할 수 있어요. 모든 것에는 제 자리가 있고, 모든 것이 제 자리에 있죠.

"Hello, world!" 프로젝트처럼 Cargo를 안 쓰는 프로젝트를 시작했다면, Cargo를 쓰는 프로젝트로 변환할 수도 있어요. 프로젝트 코드를 src 디렉터리로 옮기고 적절한 Cargo.toml 파일을 만들면 됩니다. Cargo.toml 파일을 쉽게 얻는 방법 중 하나는 cargo init을 실행하는 것인데, 자동으로 만들어줍니다.

Cargo 프로젝트 빌드하고 실행하기

이제 Cargo로 "Hello, world!" 프로그램을 빌드하고 실행할 때 무엇이 달라지는지 볼게요! hello_cargo 디렉터리에서 다음 명령을 입력해 프로젝트를 빌드하세요.

$ cargo build
   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs

이 명령은 현재 디렉터리가 아니라 target/debug/hello_cargo (Windows에서는 target\debug\hello_cargo.exe)에 실행 파일을 만듭니다. 기본 빌드가 debug 빌드이기 때문에 Cargo는 바이너리를 debug라는 이름의 디렉터리에 둡니다. 다음 명령으로 실행 파일을 실행할 수 있어요.

$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows
Hello, world!

모든 게 순조롭다면 Hello, world!가 터미널에 출력될 거예요. cargo build를 처음 실행하면 Cargo가 최상위에 새 파일, 즉 Cargo.lock도 만들게 됩니다. 이 파일은 프로젝트의 의존성 정확한 버전을 추적해요. 이 프로젝트에는 의존성이 없어서 파일 내용은 조금 비어 있습니다. 이 파일을 수동으로 바꿔야 할 일은 없어요. Cargo가 그 내용을 관리해줍니다.

방금 cargo build로 프로젝트를 빌드하고 ./target/debug/hello_cargo로 실행했는데요, cargo run을 쓰면 코드를 컴파일하고 결과 실행 파일을 실행하는 것을 하나의 명령으로 수행할 수도 있어요.

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/hello_cargo`
Hello, world!

cargo run을 쓰는 것이 cargo build를 실행하고 바이너리의 전체 경로까지 기억해야 하는 것보다 편하기 때문에, 대부분의 개발자는 cargo run을 사용합니다.

이번에는 Cargo가 hello_cargo를 컴파일한다는 출력이 보이지 않았다는 점을 눈치채셨을 거예요. Cargo는 파일이 변하지 않았다는 걸 알아차렸기 때문에 다시 빌드하지 않고 바이너리만 실행한 겁니다. 소스 코드를 수정했다면 Cargo는 실행 전에 프로젝트를 다시 빌드했을 테고, 이 출력이 보였을 거예요.

$ cargo run
   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs
     Running `target/debug/hello_cargo`
Hello, world!

Cargo는 cargo check라는 명령도 제공합니다. 이 명령은 코드가 컴파일되는지 빠르게 확인하지만 실행 파일을 만들지는 않아요.

$ cargo check
   Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs

왜 실행 파일을 원하지 않을까요? 보통 cargo checkcargo build보다 훨씬 빠른데, 실행 파일을 만드는 단계를 건너뛰기 때문이에요. 코드를 작성하면서 계속 작업 상태를 확인한다면 cargo check를 쓰는 것이 프로젝트가 여전히 컴파일되는지 알려주는 과정을 빠르게 해줍니다. 그래서 많은 Rustaceans은 프로그램을 작성하는 동안 주기적으로 cargo check를 실행해 컴파일되는지 확인합니다. 그리고 실행 파일을 쓸 준비가 되면 cargo build를 실행하죠.

지금까지 Cargo에 대해 배운 것을 정리해볼게요.

  • cargo new로 프로젝트를 만들 수 있어요.
  • cargo build로 프로젝트를 빌드할 수 있어요.
  • cargo run으로 프로젝트를 한 단계로 빌드하고 실행할 수 있어요.
  • cargo check로 바이너리를 만들지 않고 오류를 검사하도록 빌드할 수 있어요.
  • 빌드 결과를 코드가 있는 같은 디렉터리에 저장하는 대신, Cargo는 이를 target/debug 디렉터리에 저장합니다. Cargo를 쓰는 또 다른 이점은 어떤 운영체제에서 작업하든 명령어가 동일하다는 점입니다. 그래서 이 시점부터 Linux와 macOS 대 Windows를 구분한 구체적인 지침은 더 이상 제공하지 않을게요.

릴리스용 빌드

프로젝트가 마침내 릴리스할 준비가 되면 cargo build --release로 최적화를 적용해 컴파일할 수 있어요. 이 명령은 target/debug 대신 target/release에 실행 파일을 만듭니다. 최적화는 Rust 코드를 더 빠르게 실행되게 하지만, 켜면 프로그램 컴파일 시간은 길어집니다. 그래서 프로필이 두 개인 거예요. 하나는 빠르고 자주 다시 빌드하고 싶은 개발용, 다른 하나는 반복적으로 다시 빌드되지 않고 가능한 한 빠르게 실행될 최종 프로그램을 만들기 위한 것입니다. 코드 실행 시간을 벤치마킹한다면 반드시 cargo build --release를 실행하고 target/release의 실행 파일로 벤치마크하세요.

Cargo의 관례 활용하기

단순한 프로젝트에서는 Cargo가 rustc만 쓰는 것보다 많은 가치를 주지는 않지만, 프로그램이 더 복잡해질수록 그 가치를 증명할 거예요. 프로그램이 여러 파일로 커지거나 의존성이 필요해지면 빌드를 Cargo가 조정하게 두는 것이 훨씬 쉽습니다.

hello_cargo 프로젝트가 단순하긴 해도, 이제 앞으로 Rust 경력 내내 쓰게 될 실질적인 도구들을 많이 사용합니다. 실제로 기존 프로젝트에서 작업할 때는 다음 명령으로 Git을 사용해 코드를 내려받고, 그 프로젝트 디렉터리로 이동한 뒤, 빌드할 수 있어요.

$ git clone example.org/someproject
$ cd someproject
$ cargo build

Cargo에 대한 더 자세한 정보는 그 문서를 확인하세요.

요약

이번 챕터에서 이미 Rust 여정을 훌륭하게 시작했어요! 이번 챕터에서는 다음을 배웠습니다.

  • rustup으로 최신 안정 버전의 Rust를 설치하는 방법
  • 더 새로운 Rust 버전으로 업데이트하는 방법
  • 로컬에 설치된 문서를 여는 방법
  • rustc를 직접 사용해 "Hello, world!" 프로그램을 작성하고 실행하는 방법
  • Cargo의 관례에 따라 새 프로젝트를 만들고 실행하는 방법

지금은 Rust 코드를 읽고 쓰는 데 익숙해질 더 실질적인 프로그램을 만드는 좋은 시기예요. 그래서 Chapter 2에서 숫자 맞히기(guessing game) 프로그램을 만들어볼 거예요. 먼저 Rust에서 흔한 프로그래밍 개념이 어떻게 동작하는지 배우고 싶다면 Chapter 3을 보고 나서 Chapter 2로 돌아와도 돼요.

더 알아보기 (Learn more)