Cargo 기초
Cargo로 프로젝트를 생성하고 검사·빌드·실행하며 개발 빌드와 최적화된 릴리스 빌드의 차이를 익힙니다.
Cargo는 러스트의 빌드 도구이자 패키지 관리자(package manager)입니다. 코드를 빌드하고, 코드가 쓰는 외부 라이브러리를 내려받아 함께 빌드하는 일을 맡으며, 러스트에서는 이런 외부 라이브러리를 의존성(dependency)이라고 부릅니다. 앞 글의 Hello, world!처럼 의존성이 없는 프로그램에서는 Cargo가 하는 일이 빌드뿐이지만, 프로그램이 커져 의존성을 더할 때 Cargo로 시작한 프로젝트라면 그 작업이 훨씬 쉬워집니다.
러스트 프로젝트 대부분이 Cargo를 쓰므로, 이 교재도 이 글부터는 Cargo를 쓴다고 보고 설명합니다. 러스트 설치를 따라 했다면 Cargo도 이미 설치되어 있으며, 다음 명령이 버전을 출력하면 됩니다.
cargo --versionCargo로 프로젝트 만들기
projects 폴더로 돌아가 다음 명령으로 새 프로젝트를 만듭니다. 앞 글에서는 폴더와 소스 파일을 직접 만들었지만, cargo new는 프로젝트에 필요한 기본 구조를 한 번에 만들어 줍니다. 둘째 줄은 새로 생긴 프로젝트 폴더로 이동하는 명령입니다.
cargo new hello_cargo
cd hello_cargocargo 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을 열면 다음 내용이 들어 있습니다.
[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! 프로그램이 들어 있습니다.
fn main() {
println!("Hello, world!");
}앞 글에서 직접 만든 프로젝트와 다른 점은 코드가 src 폴더 안에 있고, 최상위 폴더에 Cargo.toml이 있다는 것입니다. Cargo는 소스 파일이 src 안에 있다고 보고, 최상위 폴더에는 README나 라이선스, 설정 파일처럼 코드가 아닌 파일을 둡니다. 이미 있는 폴더를 Cargo 프로젝트로 바꾸려면 그 폴더에서 cargo init을 실행하며, Cargo는 관례적인 이름의 소스 파일이 있으면 그대로 쓰고 없으면 src/main.rs를 만듭니다.
아래 그림은 Cargo 프로젝트 폴더에서 버전 관리할 입력 파일과, 빌드할 때마다 다시 만들 수 있는 target 폴더의 산출물을 나누어 보여 줍니다.
Rust · Cargo · nested structure
hello_cargo/는 입력과 재생성 가능한 산출물을 분리한다
Cargo.toml이 패키지의 의도를 선언하고 src/가 소스를 담습니다. 의존성 해석은 Cargo.lock에 기록되고, 컴파일 결과는 target/ 아래 프로필별로 모입니다.
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_cargoHello, 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입니다.
Rust · Cargo · purpose flowchart
명령은 속도가 아니라 필요한 결과로 고른다
실행, 검증, 최종 산출물 중 무엇이 필요한지 먼저 정합니다. 같은 소스를 다루더라도 명령마다 선택하는 target, 실행 단계와 기본 profile이 다릅니다.
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 buildCargo의 더 자세한 기능은 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로 사용자가 입력한 숫자를 비밀 숫자와 비교하는 게임을 만들며, 러스트의 여러 기능을 처음 써 봅니다.