본문 바로가기

Cargo 기초

Cargo로 프로젝트를 생성하고 검사·빌드·실행하며 개발 빌드와 최적화된 릴리스 빌드의 차이를 익힙니다.

Cargo는 러스트의 빌드 도구이자 패키지 관리자(package manager)입니다. 코드를 빌드하고, 코드가 쓰는 외부 라이브러리를 내려받아 함께 빌드하는 일을 맡으며, 러스트에서는 이런 외부 라이브러리를 의존성(dependency)이라고 부릅니다. 앞 글의 Hello, world!처럼 의존성이 없는 프로그램에서는 Cargo가 하는 일이 빌드뿐이지만, 프로그램이 커져 의존성을 더할 때 Cargo로 시작한 프로젝트라면 그 작업이 훨씬 쉬워집니다.

러스트 프로젝트 대부분이 Cargo를 쓰므로, 이 교재도 이 글부터는 Cargo를 쓴다고 보고 설명합니다. 러스트 설치를 따라 했다면 Cargo도 이미 설치되어 있으며, 다음 명령이 버전을 출력하면 됩니다.

cargo --version

Cargo로 프로젝트 만들기

projects 폴더로 돌아가 다음 명령으로 새 프로젝트를 만듭니다. 앞 글에서는 폴더와 소스 파일을 직접 만들었지만, cargo new는 프로젝트에 필요한 기본 구조를 한 번에 만들어 줍니다. 둘째 줄은 새로 생긴 프로젝트 폴더로 이동하는 명령입니다.

cargo new hello_cargo
cd hello_cargo

cargo new는 hello_cargo라는 폴더를 만들고 그 안에 Cargo.toml 파일과 src 폴더, src/main.rs 파일을 만듭니다. 또 이 폴더가 아직 Git 저장소 안에 있지 않다면 새 Git 저장소와 .gitignore 파일도 함께 만듭니다. Git은 파일의 변경 이력을 기록하는 버전 관리 도구로, 웹 입문 교재의 Git 기초와 협업 워크플로우에서 다룹니다. 이미 Git 저장소 안에서 실행하면 Git 파일은 만들지 않으며, 이 동작은 cargo new --vcs=git처럼 --vcs 옵션으로 바꿀 수 있고 --vcs=none을 주면 버전 관리를 설정하지 않습니다.

Cargo.toml을 열면 다음 내용이 들어 있습니다.

Cargo.toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2024"

[dependencies]

이 파일은 Cargo의 설정 형식인 TOML(Tom's Obvious, Minimal Language)로 쓰였습니다. 첫 줄의 [package]는 그 아래가 패키지 설정이라는 것을 나타내는 섹션 머리글이고, 다음 세 줄은 패키지 이름과 버전, 그리고 이 패키지가 쓰는 러스트 에디션입니다. 지금 Cargo가 새 패키지에 넣는 기본 에디션은 2024이며, 에디션은 부록 E 에디션에서 설명합니다. 마지막 [dependencies] 아래에는 프로젝트가 쓰는 의존성을 적으며, 아직은 비어 있습니다.

Cargo.toml이 관리하는 묶음을 패키지(package)라고 하고, 컴파일러가 한 번에 컴파일하는 단위를 크레이트(crate)라고 합니다. 이 패키지에서는 src/main.rs가 실행 파일을 만드는 크레이트의 시작점입니다. 외부 크레이트는 다음 글의 추리 게임에서 처음 더하며, 패키지와 크레이트의 관계는 패키지와 크레이트에서 자세히 다룹니다.

src/main.rs에는 Cargo가 만들어 둔 Hello, world! 프로그램이 들어 있습니다.

src/main.rs
fn main() {
    println!("Hello, world!");
}

앞 글에서 직접 만든 프로젝트와 다른 점은 코드가 src 폴더 안에 있고, 최상위 폴더에 Cargo.toml이 있다는 것입니다. Cargo는 소스 파일이 src 안에 있다고 보고, 최상위 폴더에는 README나 라이선스, 설정 파일처럼 코드가 아닌 파일을 둡니다. 이미 있는 폴더를 Cargo 프로젝트로 바꾸려면 그 폴더에서 cargo init을 실행하며, Cargo는 관례적인 이름의 소스 파일이 있으면 그대로 쓰고 없으면 src/main.rs를 만듭니다.

아래 그림은 Cargo 프로젝트 폴더에서 버전 관리할 입력 파일과, 빌드할 때마다 다시 만들 수 있는 target 폴더의 산출물을 나누어 보여 줍니다.

Cargo 패키지 루트 안에서 버전 관리할 입력인 Cargo.toml, src/main.rs, Cargo.lock과 다시 생성할 수 있는 target의 dev 및 release 산출물을 구분한 중첩 구조

Rust · Cargo · nested structure

hello_cargo/는 입력과 재생성 가능한 산출물을 분리한다

Cargo.toml이 패키지의 의도를 선언하고 src/가 소스를 담습니다. 의존성 해석은 Cargo.lock에 기록되고, 컴파일 결과는 target/ 아래 프로필별로 모입니다.

Cargo 패키지 내부의 입력과 생성물 중첩 구조root패키지안에version입력과생성target두영역이서로분리된다.입력은manifest,src안의mainrs,lock이며target은debug/release를포함한다.영역포함이관계이며실행순서간선이없다.같은원파일명과profile을모바일에서유지한다.hello_cargo/버전 관리 입력Cargo.tomlpackage · editiondependenciessrc/main.rs바이너리 진입점fn main()Cargo.lock선택한 의존성 버전Cargo가 갱신용도별 커밋 정책target/ 생성물debug/dev · test빠른 반복 · 디버깅release/release · bench최적화된 산출물포함 영역: 디렉터리·입력 경계생성물은 입력 소스를 대체 안 함
Cargo 프로젝트 중첩 구조 hello_cargo 패키지 루트 안의 버전 관리 입력 영역에는 Cargo.toml, src와 main.rs, Cargo.lock이 있고, 별도 생성 영역인 target에는 debug와 release 프로필 산출물이 있습니다. hello_cargo/cargo new · cargo init버전 관리되는 입력패키지 매니페스트Cargo.tomlpackage · edition · dependenciessrc/바이너리 진입점src/main.rs · fn main()선택된 의존성 버전 기록Cargo.lock · Cargo가 갱신target/ · 생성 산출물dev · testtarget/debug/빠른 반복디버깅 정보release · benchtarget/release/최적화된 산출물
hello_cargo/ PACKAGE ROOT

버전 관리되는 입력

Cargo.toml은 패키지·에디션·의존성을 선언하고, src/main.rs는 바이너리 진입점을 담습니다. Cargo.lock은 Cargo가 선택한 의존성 버전을 기록합니다.

다시 생성할 수 있는 산출물

target/debug/에는 dev·test 프로필 결과가, target/release/에는 release·bench 프로필 결과가 놓입니다.

new와 init

cargo new는 새 디렉터리를 만들고, cargo init은 기존 디렉터리에 매니페스트와 관례적 소스 구조를 더합니다.

Git 초기화 경계

기본은 Git이지만 이미 VCS 안이면 중첩 저장소를 만들지 않습니다. 필요하면 --vcs git 또는 --vcs none을 명시합니다.

Cargo.lock 정책

Cargo는 현재 추적을 기본으로 준비합니다. 애플리케이션은 보통 커밋하고, 라이브러리는 지원 정책과 검증 정책에 맞춰 결정하되 손으로 수정하지 않습니다.

소스와 설정은 의도이며 target/은 결과입니다. 이 경계를 지키면 빌드 산출물이 문서·라이선스·소스와 섞이지 않습니다.

빌드하고 실행하기

hello_cargo 폴더에서 다음 명령으로 프로젝트를 빌드합니다. rustc main.rs와 달리 파일 이름을 주지 않는 것은, Cargo가 Cargo.toml과 src/main.rs를 찾아 무엇을 컴파일할지 스스로 정하기 때문입니다. 그래서 이 명령은 Cargo.toml이 있는 프로젝트 폴더 안에서 실행합니다.

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

괄호 안의 경로는 프로젝트를 만든 위치에 따라, 마지막의 시간은 실행할 때마다 다르게 나옵니다. dev 프로필은 최적화를 하지 않고 디버그 정보를 넣는 개발용 빌드 설정이며, Cargo는 이때 만든 실행 파일을 target/debug/hello_cargo(Windows에서는 target\debug\hello_cargo.exe)에 둡니다. 이 파일을 직접 실행하면 Hello, world!가 출력됩니다.

./target/debug/hello_cargo
Hello, world!

Windows의 PowerShell에서는 .\target\debug\hello_cargo.exe로 실행합니다. 처음 빌드하면 최상위 폴더에 Cargo.lock 파일도 생기는데, 이 파일은 의존성의 정확한 버전을 기록해 다른 컴퓨터나 나중의 빌드에서도 같은 버전을 쓰게 합니다. 지금은 의존성이 없어 내용이 거의 없으며, Cargo가 알아서 관리하므로 직접 고칠 일은 없습니다.

빌드하고 실행하는 두 단계를 한 번에 하려면 cargo run을 씁니다.

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

이번 출력에는 Compiling 줄이 없습니다. 소스가 바뀌지 않았다는 것을 Cargo가 알아채고 다시 빌드하지 않은 채 기존 실행 파일을 실행했기 때문입니다. 소스를 고친 뒤 다시 cargo run을 실행하면 Compiling 줄이 나오며 프로젝트를 다시 빌드한 다음 실행합니다.

cargo check는 실행 파일을 만드는 마지막 단계를 건너뛰고, 코드가 컴파일되는지만 빠르게 확인합니다.

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

실행 파일을 만들지 않으므로 cargo build보다 대개 빠르며, 그래서 코드를 쓰는 동안에는 cargo check로 자주 확인하고 실행 파일이 필요할 때 cargo build를 쓰는 것이 일반적입니다. 코드가 의도대로 동작하는지 확인하려면 cargo test를 씁니다. 이 명령은 프로젝트의 테스트를 컴파일해 실행하는데, 아직 테스트를 하나도 쓰지 않은 이 프로젝트에서는 running 0 tests와 함께 통과한 테스트가 0개라는 결과가 나옵니다. 이것은 출력이 맞는지 검사했다는 뜻이 아니라 검사할 테스트가 없었다는 뜻이며, 테스트를 쓰는 방법은 테스트 작성 방법에서 다룹니다.

릴리스 빌드

프로그램을 배포할 때는 cargo build --release로 최적화한 릴리스 빌드를 만듭니다. 명령에는 --release 하나만 더했지만, 출력의 둘째 줄에 나오는 프로필 이름과 괄호 안의 설명이 앞의 cargo build와 다르다는 점을 보세요.

cargo build --release
   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
    Finished `release` profile [optimized] target(s) in 0.32s

릴리스 빌드는 release 프로필로 최적화를 적용하며, 실행 파일을 target/debug가 아니라 target/release에 둡니다. 최적화 때문에 컴파일은 더 오래 걸리지만 만들어진 프로그램은 대개 더 빠르게 실행됩니다. 개발할 때는 빌드를 자주 하므로 빨리 빌드되는 dev 프로필을 쓰고, 사용자에게 줄 프로그램은 한 번 빌드해 오래 쓰므로 빠르게 실행되는 release 프로필을 씁니다. 같은 이유로 코드의 실행 속도를 잴 때는 릴리스 빌드로 재야 합니다.

지금까지 본 명령은 아래 그림처럼 필요한 결과에 따라 고릅니다. 테스트를 실행할 때는 cargo test, 프로그램을 실행해 볼 때는 cargo run, 컴파일되는지만 볼 때는 cargo check, 실행 파일이 필요하면 cargo build, 배포용이면 cargo build --release입니다.

테스트 여부, 프로그램 실행 여부, 최종 산출물 필요 여부와 릴리스 기준 여부를 차례로 판단해 cargo run, test, check, build, build release 중 하나를 고르는 흐름

Rust · Cargo · purpose flowchart

명령은 속도가 아니라 필요한 결과로 고른다

실행, 검증, 최종 산출물 중 무엇이 필요한지 먼저 정합니다. 같은 소스를 다루더라도 명령마다 선택하는 target, 실행 단계와 기본 profile이 다릅니다.

목적에 따라 Cargo 명령을 한 갈래 선택한다원4판정5명령터미널9노드8간선이다.테스트예test/아니요실행판정,실행예run/아니요산출물판정,산출물아니요check/예release판정,release아니요build/예buildrelease다.터미널사이시간간선없다.모바일진행우회는불필요한명령을실행시키지않는다.동작 테스트?검사할 테스트가 있음테스트 실행cargo test프로그램 실행?바이너리·예제빌드 뒤 실행cargo run산출물 필요?실행 파일 / 라이브러리빠른 정적 확인cargo checkrelease 최적화?빌드 프로필의 목적dev 산출물cargo buildrelease 산출물cargo build--release예아니요예아니요아니요예아니요예각 명령은 선택 결과모든 명령을 차례로 실행하지 않음
Cargo 명령 선택 흐름 동작 테스트가 목적이면 cargo test, 바이너리나 예제를 실행하려면 cargo run을 선택합니다. 실행이 필요 없으면 최종 산출물과 release 최적화 필요 여부에 따라 cargo check, cargo build 또는 cargo build --release를 선택합니다. 동작 테스트가필요한가?아니요프로그램을실행할까?아니요최종 산출물이필요한가?예release 최적화가필요한가?예예예아니요아니요테스트 실행cargo test바이너리·예제 실행cargo run빠른 오류 확인cargo checkdev 산출물cargo buildrelease 산출물cargo build --release

cargo run

바이너리나 예제를 dev 프로필로 빌드하고 곧바로 실행해 결과를 봅니다.

cargo test

단위·통합·문서 테스트를 컴파일하고 실행하며, 예제 대상의 컴파일도 확인합니다.

cargo check

최종 코드 생성을 생략해 선택된 패키지와 의존성의 컴파일 오류를 빠르게 찾습니다.

cargo build

dev 프로필로 라이브러리와 바이너리 산출물을 target/debug/에 만듭니다.

cargo build --release

release 프로필로 최적화하고 결과를 target/release/에 만듭니다.

기본 프로필

check·build·run은 dev, test는 test 프로필을 사용합니다. test 결과도 역사적으로 target/debug/에 놓입니다.

대상 범위

기본 선택은 명령마다 다릅니다. 테스트 대상까지 정적으로 검사하려면 cargo check --all-targets처럼 범위를 명시합니다.

성능 측정

dev 빌드는 반복 속도와 디버깅을 우선합니다. 실행 성능 비교는 최적화된 release 산출물을 기준으로 합니다.

모든 명령은 target/ 캐시를 재사용할 수 있습니다. 가장 빠른 명령을 찾기보다 지금 필요한 검증과 산출물을 먼저 고릅니다.

관례로서의 Cargo

hello_cargo처럼 단순한 프로젝트에서는 Cargo가 rustc를 직접 쓰는 것보다 크게 나을 것이 없지만, 파일이 여러 개이고 의존성이 있는 프로젝트에서는 Cargo에 빌드를 맡기는 편이 훨씬 쉽습니다. 또 러스트 프로젝트 대부분이 같은 방식으로 빌드되므로, 다른 사람의 프로젝트도 다음 명령만으로 내려받아 빌드할 수 있습니다.

git clone example.org/someproject
cd someproject
cargo build

Cargo의 더 자세한 기능은 Cargo 공식 문서와 13장의 릴리즈 프로필 설정부터 이어서 다룹니다.

정리

  • cargo new는 Cargo.toml과 src/main.rs를 갖춘 프로젝트를 만들고, Git 저장소 밖이라면 Git 저장소도 함께 만듭니다.
  • Cargo.toml은 패키지 이름과 버전, 에디션, 의존성을 적는 설정 파일입니다.
  • cargo build는 target/debug에 실행 파일을 만들고, cargo run은 빌드한 뒤 실행하며, 바뀐 것이 없으면 다시 빌드하지 않습니다.
  • cargo check는 실행 파일 없이 컴파일 여부만 빠르게 확인하고, cargo test는 테스트를 실행합니다.
  • cargo build --release는 최적화한 실행 파일을 target/release에 만들며, 실행 속도는 릴리스 빌드로 잽니다.
  • Cargo.lock은 의존성의 정확한 버전을 기록하며 Cargo가 관리합니다.

연습 문제

1. 어떤 명령을 쓸까

hello_cargo를 빌드한 뒤 src/main.rs의 문자열만 Ready로 바꾸어 저장했습니다. (가) cargo check가 성공한 뒤 target/debug의 실행 파일을 직접 실행하면 Ready가 나오나요? (나) 바뀐 문구를 바로 실행해 보려면 어떤 명령이 알맞나요?

해설 보기

(가) cargo check는 실행 파일을 새로 만들지 않으므로, target/debug의 실행 파일은 그대로이고 이전 문구 Hello, world!가 나옵니다. (나) cargo run을 쓰면 바뀐 소스를 빌드한 뒤 바로 실행해 Ready를 보여 줍니다. cargo build로 빌드한 뒤 실행 파일을 직접 실행해도 같은 결과입니다.

2. 속도를 잴 때

두 가지 정렬 방법의 실행 속도를 비교하려고 cargo run으로 각각 실행해 시간을 쟀습니다. 이 측정에서 무엇이 잘못되었고, 어떻게 고쳐야 하나요?

해설 보기

cargo run은 기본으로 최적화하지 않는 dev 프로필로 빌드하므로, 실제 배포될 프로그램의 속도와 다를 수 있습니다. cargo run --release나 cargo build --release로 만든 실행 파일로 재고, 두 방법에 같은 입력과 같은 측정 조건을 써야 공정하게 비교할 수 있습니다.

다음 글 추리 게임에서는 지금까지 익힌 Cargo로 사용자가 입력한 숫자를 비밀 숫자와 비교하는 게임을 만들며, 러스트의 여러 기능을 처음 써 봅니다.