이미지 로딩 중...

Rust 입문 가이드 10 Hello World와 Cargo 프로젝트 생성 - 슬라이드 1/11
A

AI Generated

2025. 11. 13. · 4 Views

Rust 입문 가이드 10 Hello World와 Cargo 프로젝트 생성

Rust 프로그래밍의 시작점인 Hello World 프로그램 작성과 Cargo 프로젝트 관리 도구의 기본 사용법을 배웁니다. 실제 개발 환경에서 프로젝트를 생성하고 빌드하며 실행하는 과정을 단계별로 익힐 수 있습니다.


목차

  1. Hello World 프로그램 - Rust의 첫 발걸음
  2. Cargo 소개 - Rust의 프로젝트 관리자
  3. 새 프로젝트 생성하기 - cargo new 명령
  4. Cargo.toml 파일 이해하기 - 프로젝트 설정의 핵심
  5. 프로젝트 빌드와 실행 - cargo build & cargo run
  6. 컴파일 체크와 테스트 - cargo check & cargo test
  7. 프로젝트 구조 이해하기 - src와 target 디렉토리
  8. 의존성 추가하기 - crates.io 활용
  9. 에디션 이해하기 - Rust 2021 Edition
  10. 첫 실전 프로젝트 - Hello User 인터렉티브 프로그램

1. Hello World 프로그램 - Rust의 첫 발걸음

시작하며

여러분이 새로운 프로그래밍 언어를 배울 때 가장 먼저 하는 일이 무엇인가요? 대부분의 개발자들은 "Hello World" 프로그램을 작성하면서 시작합니다.

이것은 단순해 보이지만, 언어의 기본 구조와 실행 환경을 이해하는 가장 효과적인 방법입니다. Rust에서 Hello World는 특별한 의미를 가집니다.

컴파일 과정, 메모리 안전성 체크, 그리고 Rust 특유의 문법을 처음 만나는 순간이기 때문입니다. 겨우 몇 줄의 코드지만, 여기에는 Rust의 철학이 담겨 있습니다.

이 카드에서는 가장 간단한 Rust 프로그램을 작성하고 실행하는 방법을 배웁니다. 이것이 바로 여러분의 Rust 개발 여정의 시작점입니다.

개요

간단히 말해서, Hello World 프로그램은 "Hello, world!"라는 텍스트를 화면에 출력하는 프로그램입니다. 이 프로그램이 중요한 이유는 컴파일러가 제대로 설치되었는지, 개발 환경이 올바르게 구성되었는지 확인할 수 있기 때문입니다.

또한 Rust의 기본 문법인 함수 정의, 매크로 사용, 그리고 세미콜론 규칙을 처음 접하게 됩니다. 기존 C나 Java에서 printf나 System.out.println을 사용했다면, Rust에서는 println!

매크로를 사용합니다. 느낌표(!)가 붙은 이유는 이것이 함수가 아닌 매크로이기 때문입니다.

Rust의 Hello World는 main 함수로 시작되며, 이는 프로그램의 진입점입니다. 모든 실행 가능한 Rust 프로그램은 반드시 main 함수를 가져야 하며, 이 안에 작성된 코드가 실행됩니다.

코드 예제

// main.rs - Rust 프로그램의 진입점
fn main() {
    // println!은 매크로로, 텍스트를 화면에 출력하고 줄바꿈합니다
    println!("Hello, world!");

    // 여러 줄 출력하기
    println!("Welcome to Rust programming!");
    println!("Let's build something amazing.");
}

설명

이것이 하는 일: 이 프로그램은 표준 출력(터미널)에 "Hello, world!"라는 메시지를 표시하고 자동으로 줄바꿈을 추가합니다. 첫 번째로, fn main()은 프로그램의 진입점을 정의합니다.

fn은 "function"의 약자이며, main은 특별한 함수 이름입니다. Rust 컴파일러는 실행 파일을 만들 때 항상 main 함수를 찾아서 프로그램을 시작합니다.

중괄호 {}는 함수의 본문을 감싸며, 이 안에 실행할 코드를 작성합니다. 그 다음으로, println!("Hello, world!")이 실행됩니다.

println!은 매크로이며, 느낌표(!)가 이를 나타냅니다. 매크로는 컴파일 시점에 코드를 생성하는 강력한 기능으로, 함수보다 더 유연하게 동작합니다.

문자열은 큰따옴표("")로 감싸며, 세미콜론(;)으로 문장을 종료합니다. println!

매크로는 자동으로 줄바꿈을 추가하기 때문에 다음 출력이 새로운 줄에 나타납니다. 만약 줄바꿈 없이 출력하고 싶다면 print!

매크로를 사용할 수 있습니다. 여러분이 이 코드를 작성하고 컴파일하면 실행 가능한 바이너리 파일이 생성됩니다.

이 파일을 실행하면 터미널에 메시지가 표시되며, 이것은 C/C++처럼 네이티브 코드로 실행되기 때문에 매우 빠릅니다. Rust는 가비지 컬렉션 없이도 메모리 안전성을 보장하며, 이미 이 간단한 프로그램에서도 그 원칙이 적용되고 있습니다.

실전 팁

💡 rustc main.rs 명령으로 직접 컴파일할 수 있지만, 실무에서는 Cargo를 사용하는 것이 표준입니다

💡 println! 대신 print!를 사용하면 줄바꿈 없이 출력되며, 여러 print!를 연속으로 사용할 때 유용합니다

💡 문자열 포맷팅을 위해 println!("{}", 변수명) 형식을 사용할 수 있으며, 이는 나중에 자주 활용됩니다

💡 VSCode에서 rust-analyzer 확장을 설치하면 코드 자동완성과 실시간 오류 체크를 받을 수 있습니다

💡 컴파일 오류가 발생하면 Rust 컴파일러는 매우 친절한 오류 메시지와 해결 방법을 제공하므로 꼭 읽어보세요


2. Cargo 소개 - Rust의 프로젝트 관리자

시작하며

여러분이 실무에서 프로젝트를 진행할 때 파일 구조는 어떻게 관리하시나요? 의존성 라이브러리는 어떻게 추가하고, 빌드는 어떻게 자동화하시나요?

매번 수동으로 rustc를 실행하고 파일 경로를 관리하는 것은 비효율적이고 오류가 발생하기 쉽습니다. 이런 문제를 해결하기 위해 대부분의 현대 프로그래밍 언어는 전용 빌드 도구를 제공합니다.

npm(Node.js), pip(Python), Maven(Java)처럼 말이죠. Rust의 경우 이 역할을 Cargo가 담당합니다.

Cargo는 단순한 빌드 도구를 넘어서 프로젝트 생성, 의존성 관리, 테스트 실행, 문서 생성까지 모든 것을 통합적으로 처리합니다. 실무에서 Rust 개발자들은 거의 100% Cargo를 사용합니다.

개요

간단히 말해서, Cargo는 Rust의 공식 패키지 매니저이자 빌드 시스템입니다. Cargo가 필요한 이유는 프로젝트가 커질수록 수동 관리가 불가능해지기 때문입니다.

여러 소스 파일, 외부 라이브러리(crate), 테스트 코드, 빌드 설정 등을 일일이 관리하는 것은 현실적으로 불가능합니다. 예를 들어, 웹 서버를 만들 때 HTTP 라이브러리, 데이터베이스 드라이버, JSON 파서 등 수십 개의 의존성을 관리해야 하는 경우가 많습니다.

기존에 Makefile이나 CMake로 빌드 스크립트를 직접 작성했다면, 이제는 Cargo.toml 파일에 간단한 설정만 추가하면 됩니다. Cargo가 모든 컴파일, 링킹, 의존성 다운로드를 자동으로 처리해줍니다.

Cargo의 핵심 특징은 세 가지입니다. 첫째, 표준화된 프로젝트 구조를 제공하여 모든 Rust 프로젝트가 일관된 형태를 유지합니다.

둘째, crates.io라는 중앙 패키지 저장소와 연동되어 수천 개의 라이브러리를 쉽게 사용할 수 있습니다. 셋째, 증분 컴파일과 병렬 빌드를 지원하여 대규모 프로젝트에서도 빠른 빌드 속도를 유지합니다.

코드 예제

# Cargo 설치 확인 및 버전 체크
cargo --version

# 결과 예시:
# cargo 1.75.0 (1d8b05cdd 2023-11-20)

# Cargo의 주요 명령어들
cargo new my_project      # 새 프로젝트 생성
cargo build              # 프로젝트 빌드 (디버그 모드)
cargo run                # 빌드 + 실행을 한 번에
cargo test               # 테스트 실행
cargo check              # 컴파일 체크만 (빌드보다 빠름)
cargo build --release    # 최적화된 릴리스 빌드

설명

이것이 하는 일: Cargo는 Rust 프로젝트의 전체 생명주기를 관리하는 통합 도구입니다. 첫 번째로, cargo --version 명령은 Cargo가 제대로 설치되어 있는지 확인합니다.

Rust를 rustup으로 설치했다면 Cargo는 자동으로 함께 설치됩니다. 버전 정보가 출력되면 정상적으로 설치된 것입니다.

그 다음으로, 각 명령어가 특정 작업을 담당합니다. cargo new는 표준 프로젝트 구조를 자동으로 생성하며, src 디렉토리와 Cargo.toml 설정 파일을 만듭니다.

cargo build는 소스 코드를 컴파일하여 target/debug 디렉토리에 실행 파일을 생성합니다. 디버그 모드는 빌드가 빠르지만 실행 속도는 느립니다.

cargo run은 개발 중 가장 자주 사용하는 명령어입니다. 빌드와 실행을 한 번에 처리하며, 코드가 변경되지 않았으면 빌드를 건너뛰고 바로 실행합니다.

cargo check는 컴파일 오류만 체크하고 실행 파일을 생성하지 않기 때문에 build보다 훨씬 빠르며, 코드 작성 중 빠른 피드백을 받을 때 유용합니다. cargo build --release는 프로덕션 배포용 빌드를 만듭니다.

컴파일 시간은 오래 걸리지만 최적화가 적용되어 실행 속도가 훨씬 빠릅니다. 벤치마크나 성능 측정 시에는 반드시 릴리스 모드를 사용해야 정확한 결과를 얻을 수 있습니다.

여러분이 이러한 Cargo 명령어를 익히면 프로젝트 관리가 자동화되고, 팀원들과 일관된 개발 환경을 공유할 수 있습니다. Cargo.toml 파일만 공유하면 누구나 동일한 의존성으로 프로젝트를 빌드할 수 있으며, 이는 협업에서 매우 중요합니다.

실전 팁

💡 cargo check를 자주 사용하면 빌드 시간을 절약하면서 실시간으로 오류를 확인할 수 있습니다

💡 cargo run -- arg1 arg2 형식으로 프로그램에 인자를 전달할 수 있으며, --뒤의 내용이 프로그램으로 전달됩니다

💡 VSCode에서 저장할 때마다 자동으로 cargo check가 실행되도록 설정하면 개발 효율이 크게 향상됩니다

💡 cargo build --release는 디버그 빌드보다 10배 이상 빠를 수 있으므로 성능 테스트 시 반드시 사용하세요

💡 CARGO_INCREMENTAL=1 환경 변수를 설정하면 증분 컴파일이 활성화되어 재빌드가 더 빠릅니다


3. 새 프로젝트 생성하기 - cargo new 명령

시작하며

여러분이 새로운 Rust 프로젝트를 시작할 때 어떤 폴더 구조를 만들어야 할까요? main.rs는 어디에 두고, 설정 파일은 어떤 형식으로 작성해야 할까요?

매번 수동으로 디렉토리를 만들고 파일을 생성하는 것은 번거롭고 실수하기 쉽습니다. Cargo는 이런 반복적인 작업을 자동화해줍니다.

단 한 줄의 명령어로 완벽하게 구성된 프로젝트 템플릿을 생성할 수 있습니다. Git 저장소까지 자동으로 초기화되어 버전 관리를 바로 시작할 수 있습니다.

이 카드에서는 cargo new 명령으로 프로젝트를 생성하고, 생성된 파일들의 역할을 이해하는 방법을 배웁니다. 이것이 모든 Rust 프로젝트의 표준 시작점입니다.

개요

간단히 말해서, cargo new는 새로운 Rust 프로젝트를 위한 표준 디렉토리 구조와 초기 파일들을 자동으로 생성하는 명령어입니다. 이 명령이 중요한 이유는 Rust 커뮤니티 전체가 동일한 프로젝트 구조를 사용하기 때문입니다.

어떤 Rust 프로젝트를 열어봐도 src/main.rs, Cargo.toml, .gitignore가 같은 위치에 있어 익숙합니다. 예를 들어, 오픈소스 라이브러리를 받아서 분석하거나 기여할 때 이러한 일관성이 큰 도움이 됩니다.

기존에 mkdir, touch 같은 명령으로 일일이 파일을 만들었다면, 이제는 cargo new 한 번이면 끝입니다. 프로젝트 이름, 작성자 정보, 버전까지 자동으로 설정됩니다.

cargo new는 두 가지 타입의 프로젝트를 만들 수 있습니다. 기본은 실행 가능한 바이너리 프로젝트(--bin)이고, 라이브러리 프로젝트(--lib)도 만들 수 있습니다.

바이너리는 main.rs를 가지며 실행 파일을 만들고, 라이브러리는 lib.rs를 가지며 다른 프로젝트에서 사용할 수 있는 코드를 만듭니다.

코드 예제

# 새로운 바이너리 프로젝트 생성 (기본)
cargo new hello_rust

# 생성되는 파일 구조:
# hello_rust/
# ├── Cargo.toml        # 프로젝트 메타데이터와 의존성 설정
# ├── .gitignore        # Git에서 제외할 파일 목록
# └── src/
#     └── main.rs       # 메인 소스 코드

# 라이브러리 프로젝트 생성
cargo new my_library --lib

# 특정 버전 관리 시스템 없이 생성
cargo new my_project --vcs none

설명

이것이 하는 일: cargo new 명령은 완전히 구성된 Rust 프로젝트 템플릿을 생성하고 Git 저장소를 초기화합니다. 첫 번째로, cargo new hello_rust를 실행하면 hello_rust라는 이름의 디렉토리가 생성됩니다.

이 이름은 프로젝트의 루트 디렉토리 이름이자 컴파일된 실행 파일의 이름이 됩니다. Rust 커뮤니티 관례상 프로젝트 이름은 소문자와 언더스코어를 사용합니다.

그 다음으로, 내부에 세 가지 중요한 요소가 생성됩니다. Cargo.toml 파일은 프로젝트 설정 파일로, 프로젝트 이름, 버전, Rust 에디션, 의존성 목록이 포함됩니다.

TOML은 읽기 쉬운 설정 파일 형식으로, JSON보다 간결하고 YAML보다 명확합니다. src 디렉토리는 모든 소스 코드가 들어가는 곳입니다.

main.rs 파일은 프로그램의 진입점을 포함하며, 이미 기본 Hello World 코드가 작성되어 있습니다. 여러분은 즉시 cargo run으로 실행해볼 수 있습니다.

.gitignore 파일은 버전 관리에서 제외할 파일 목록을 정의합니다. target/ 디렉토리(빌드 결과물)와 Cargo.lock 파일(바이너리 프로젝트가 아닌 경우)이 자동으로 추가되어 있습니다.

이를 통해 불필요한 파일이 Git에 커밋되는 것을 방지합니다. --lib 옵션을 사용하면 라이브러리 프로젝트가 생성되며, main.rs 대신 lib.rs 파일이 만들어집니다.

라이브러리는 실행되지 않고 다른 프로젝트에서 import하여 사용됩니다. crates.io에 배포하려면 라이브러리 형태로 만들어야 합니다.

여러분이 이 명령을 사용하면 몇 초 안에 완벽한 프로젝트 구조가 만들어지며, 즉시 코드 작성을 시작할 수 있습니다. 팀 프로젝트에서도 모든 팀원이 동일한 구조를 사용하므로 협업이 매우 원활합니다.

실전 팁

💡 프로젝트 이름에 하이픈(-)을 사용하면 자동으로 언더스코어(_)로 변환되어 Rust 식별자 규칙을 따릅니다

💡 cargo new를 실행하면 자동으로 git init도 실행되므로, 이미 Git 저장소 안에 있다면 --vcs none 옵션을 사용하세요

💡 cargo init 명령을 사용하면 현재 디렉토리에 프로젝트 파일을 생성할 수 있어 기존 폴더를 Rust 프로젝트로 변환할 때 유용합니다

💡 생성 직후 cargo run을 실행하면 기본 Hello World가 실행되어 환경이 제대로 구성되었는지 즉시 확인할 수 있습니다

💡 회사 프로젝트에서는 Cargo.toml의 authors 필드가 자동으로 Git 설정에서 가져오므로 git config로 정보를 미리 설정해두세요


4. Cargo.toml 파일 이해하기 - 프로젝트 설정의 핵심

시작하며

여러분이 프로젝트에 외부 라이브러리를 추가하거나 프로젝트 정보를 수정하려면 어떻게 해야 할까요? package.json(Node.js)이나 requirements.txt(Python)처럼 Rust에도 프로젝트 설정을 관리하는 파일이 필요합니다.

Cargo.toml은 Rust 프로젝트의 모든 메타데이터와 설정을 담고 있는 중앙 설정 파일입니다. 프로젝트 이름, 버전, 사용할 Rust 에디션, 그리고 가장 중요한 의존성 목록이 여기에 정의됩니다.

이 파일을 제대로 이해하면 프로젝트 관리가 훨씬 수월해집니다. 라이브러리 추가, 빌드 옵션 설정, 최적화 레벨 조정 등 모든 것이 이 파일에서 이루어집니다.

개요

간단히 말해서, Cargo.toml은 Rust 프로젝트의 매니페스트 파일로, 프로젝트 정보와 의존성을 정의하는 TOML 형식의 설정 파일입니다. 이 파일이 필요한 이유는 Cargo가 프로젝트를 빌드할 때 필요한 모든 정보를 여기서 읽어오기 때문입니다.

어떤 라이브러리를 다운로드해야 하는지, 어떤 Rust 버전 기능을 사용할 수 있는지, 최적화는 어떻게 할지 등 모든 결정이 이 파일을 기반으로 이루어집니다. 예를 들어, serde 라이브러리를 사용하려면 [dependencies] 섹션에 한 줄만 추가하면 Cargo가 자동으로 다운로드하고 빌드에 포함시킵니다.

기존에 복잡한 Makefile이나 빌드 스크립트를 작성했다면, Cargo.toml은 훨씬 간단하고 선언적입니다. "무엇을 원하는지"만 작성하면 Cargo가 "어떻게 할지"를 알아서 처리합니다.

Cargo.toml의 핵심 섹션은 세 가지입니다. [package]는 프로젝트 메타데이터(이름, 버전, 에디션)를 정의하고, [dependencies]는 런타임에 필요한 라이브러리를 나열하며, [dev-dependencies]는 개발과 테스트에만 필요한 라이브러리를 지정합니다.

추가로 [profile] 섹션에서 빌드 최적화 옵션을 세밀하게 조정할 수 있습니다.

코드 예제

# Cargo.toml 파일 예시
[package]
name = "hello_rust"              # 프로젝트 이름 (크레이트 이름)
version = "0.1.0"                # 시맨틱 버저닝 (major.minor.patch)
edition = "2021"                 # Rust 에디션 (2015, 2018, 2021)
authors = ["Your Name <you@example.com>"]

# 런타임 의존성 (실제 프로그램에서 사용)
[dependencies]
serde = "1.0"                    # JSON 직렬화 라이브러리
tokio = { version = "1.0", features = ["full"] }  # 비동기 런타임

# 개발/테스트용 의존성
[dev-dependencies]
criterion = "0.5"                # 벤치마킹 도구

설명

이것이 하는 일: Cargo.toml 파일은 프로젝트의 모든 메타데이터와 빌드 설정을 TOML 형식으로 저장합니다. 첫 번째로, [package] 섹션은 프로젝트의 기본 정보를 정의합니다.

name은 컴파일된 바이너리 파일 이름이 되며, crates.io에 배포할 때도 이 이름이 사용됩니다. version은 시맨틱 버저닝을 따르며, API 호환성을 명시적으로 표현합니다.

edition은 사용할 Rust 언어 기능 세트를 결정하는데, 2021 에디션이 가장 최신이며 async/await 같은 현대적 기능을 완전히 지원합니다. 그 다음으로, [dependencies] 섹션에서 외부 크레이트(라이브러리)를 선언합니다.

serde = "1.0"은 "1.0 이상 2.0 미만"의 serde를 사용한다는 의미입니다. Cargo는 시맨틱 버저닝에 따라 1.x 버전 내에서 자동으로 호환 가능한 최신 버전을 선택합니다.

이를 통해 버그 수정과 새 기능을 자동으로 받으면서도 호환성을 유지할 수 있습니다. tokio 예시처럼 중괄호 문법을 사용하면 추가 옵션을 지정할 수 있습니다.

features = ["full"]은 tokio의 모든 기능을 활성화하라는 의미입니다. Rust 라이브러리는 기본적으로 최소 기능만 제공하고, 필요한 기능을 명시적으로 활성화하도록 설계되어 컴파일 시간과 바이너리 크기를 줄입니다.

[dev-dependencies]는 cargo test나 cargo bench 실행 시에만 사용되는 의존성입니다. 예를 들어 criterion 벤치마킹 라이브러리는 성능 측정에만 필요하므로, 최종 프로그램에는 포함되지 않습니다.

이렇게 분리하면 프로덕션 빌드 크기를 줄일 수 있습니다. 여러분이 새로운 라이브러리를 추가하려면 [dependencies]에 한 줄만 추가하면 됩니다.

다음 cargo build 실행 시 자동으로 다운로드되어 빌드에 포함됩니다. crates.io에서 라이브러리를 검색하면 정확한 추가 방법이 표시되므로 복사해서 붙여넣기만 하면 됩니다.

실전 팁

💡 cargo add serde 명령을 사용하면 Cargo.toml을 직접 수정하지 않고도 의존성을 추가할 수 있습니다 (Cargo 1.62 이상)

💡 의존성 버전에 ^1.0 (캐럿)이나 ~1.0 (틸드)를 사용할 수 있지만, 기본 "1.0" 표기가 가장 일반적이고 안전합니다

💡 features = [] 배열로 필요한 기능만 선택하면 컴파일 시간이 크게 단축되며, 특히 대형 라이브러리에서 효과적입니다

💡 version = "=1.2.3"처럼 정확한 버전을 고정하면 재현 가능한 빌드를 보장할 수 있지만, 보안 업데이트를 자동으로 받지 못합니다

💡 [profile.release] 섹션에서 opt-level = 3 같은 최적화 레벨을 조정하여 빌드 시간과 실행 속도를 균형있게 맞출 수 있습니다


5. 프로젝트 빌드와 실행 - cargo build & cargo run

시작하며

여러분이 코드를 작성한 후 실제로 실행해보려면 어떤 과정을 거쳐야 할까요? 소스 코드는 그 자체로는 실행되지 않으며, 컴파일이라는 변환 과정을 거쳐 실행 가능한 바이너리 파일이 되어야 합니다.

Rust는 컴파일 언어이기 때문에 코드를 수정할 때마다 다시 컴파일해야 합니다. Python이나 JavaScript처럼 즉시 실행되지 않지만, 그 대신 실행 속도가 월등히 빠르고 많은 오류를 컴파일 시점에 잡아낼 수 있습니다.

Cargo는 이 빌드 프로세스를 매우 간단하게 만들어줍니다. cargo build로 컴파일하고, cargo run으로 빌드와 실행을 한 번에 처리할 수 있습니다.

개발 중에는 cargo run만 기억하면 됩니다.

개요

간단히 말해서, cargo build는 소스 코드를 컴파일하여 실행 파일을 생성하고, cargo run은 빌드와 실행을 한 번에 수행하는 명령어입니다. 이 명령들이 중요한 이유는 개발 워크플로우의 핵심이기 때문입니다.

코드를 작성하고, 빌드하고, 실행하고, 오류를 수정하는 이 사이클을 하루에 수백 번 반복합니다. 예를 들어, 웹 서버를 개발할 때 라우트를 추가하거나 데이터베이스 쿼리를 수정할 때마다 cargo run으로 즉시 테스트할 수 있습니다.

기존에 gcc나 rustc를 직접 실행하여 컴파일 옵션을 일일이 지정했다면, Cargo는 모든 것을 자동화합니다. 소스 파일이 여러 개여도, 의존성이 많아도 걱정 없습니다.

cargo build는 두 가지 모드로 작동합니다. 기본 디버그 모드는 빌드가 빠르지만 최적화되지 않아 실행 속도가 느립니다.

--release 플래그를 추가하면 최적화된 릴리스 빌드를 만들어 실행 속도가 수십 배 빠르지만 컴파일 시간은 더 오래 걸립니다. 개발 중에는 디버그 모드를, 배포나 벤치마킹에는 릴리스 모드를 사용합니다.

코드 예제

# 디버그 모드로 빌드 (개발용 - 빠른 컴파일)
cargo build
# 결과: target/debug/프로젝트명 실행 파일 생성

# 릴리스 모드로 빌드 (배포용 - 최적화)
cargo build --release
# 결과: target/release/프로젝트명 실행 파일 생성

# 빌드 + 실행을 한 번에 (가장 자주 사용)
cargo run

# 릴리스 모드로 빌드하고 실행
cargo run --release

# 프로그램에 인자 전달하기
cargo run -- arg1 arg2

설명

이것이 하는 일: 이 명령들은 Rust 소스 코드를 네이티브 실행 파일로 컴파일하고 실행합니다. 첫 번째로, cargo build를 실행하면 Cargo는 여러 단계를 거칩니다.

먼저 Cargo.toml을 읽어 의존성을 확인하고, 필요하면 crates.io에서 라이브러리를 다운로드합니다. 그 다음 모든 소스 파일을 컴파일하여 오브젝트 파일을 만들고, 마지막으로 링킹을 통해 실행 가능한 바이너리를 생성합니다.

결과물은 target/debug 디렉토리에 저장됩니다. 디버그 빌드는 개발 중 빠른 피드백을 위해 최적화를 최소화합니다.

대신 디버그 심볼이 포함되어 있어 gdb나 lldb 같은 디버거로 쉽게 분석할 수 있습니다. assert!

같은 디버그 검증도 활성화되어 있어 버그를 빨리 발견할 수 있습니다. cargo build --release는 완전히 다른 결과물을 만듭니다.

LLVM 최적화가 적용되어 불필요한 코드가 제거되고, 루프가 언롤되며, 인라이닝이 공격적으로 이루어집니다. 결과적으로 실행 속도가 디버그 빌드보다 5~50배 빠를 수 있습니다.

대신 컴파일 시간은 2~10배 더 걸리므로 개발 중에는 사용하지 않습니다. cargo run은 개발 중 가장 편리한 명령입니다.

코드가 변경되었는지 자동으로 확인하고, 변경된 부분만 재컴파일하는 증분 컴파일을 수행합니다. 변경 사항이 없으면 빌드를 건너뛰고 바로 실행하므로 시간을 절약합니다.

-- 다음에 인자를 추가하면 프로그램에 전달되므로, 명령줄 도구를 개발할 때 매우 유용합니다. 여러분이 코드를 수정한 후 cargo run만 실행하면 됩니다.

오류가 있으면 컴파일 단계에서 상세한 오류 메시지를 보여주고, 성공하면 즉시 프로그램이 실행됩니다. 이 간단한 워크플로우로 하루에도 수백 번 코드를 테스트할 수 있습니다.

실전 팁

💡 cargo run -q 또는 --quiet 플래그로 컴파일 메시지를 숨기고 프로그램 출력만 볼 수 있습니다

💡 target/ 디렉토리는 크기가 매우 커질 수 있으므로 (수 GB), cargo clean으로 정기적으로 삭제하여 디스크 공간을 확보하세요

💡 환경 변수 RUST_BACKTRACE=1 cargo run으로 실행하면 패닉 시 상세한 스택 트레이스를 볼 수 있습니다

💡 CI/CD 파이프라인에서는 항상 cargo build --release를 사용하여 프로덕션 품질의 바이너리를 생성하세요

💡 여러 바이너리가 있는 프로젝트에서는 cargo run --bin 바이너리명으로 특정 실행 파일을 선택할 수 있습니다


6. 컴파일 체크와 테스트 - cargo check & cargo test

시작하며

여러분이 코드를 작성하면서 문법 오류나 타입 오류를 확인하고 싶을 때, 매번 전체 빌드를 기다려야 한다면 얼마나 답답할까요? 특히 대형 프로젝트에서는 빌드에 수 분이 걸릴 수 있습니다.

더 나아가 코드가 제대로 작동하는지 확인하려면 테스트가 필요합니다. 수동으로 매번 실행해보는 것은 시간 낭비이며, 회귀 버그(이전에 고친 버그가 다시 발생)를 놓치기 쉽습니다.

Cargo는 이 두 가지 문제를 해결합니다. cargo check로 빠르게 오류를 확인하고, cargo test로 자동화된 테스트를 실행할 수 있습니다.

이 두 명령은 코드 품질을 유지하는 핵심 도구입니다.

개요

간단히 말해서, cargo check는 코드 오류만 검사하고 실행 파일을 생성하지 않아 빠르며, cargo test는 프로젝트의 모든 테스트 코드를 실행하여 정확성을 검증합니다. cargo check가 중요한 이유는 개발 중 빠른 피드백 루프를 제공하기 때문입니다.

코드를 작성하면서 실시간으로 오류를 확인할 수 있어, 긴 빌드를 기다릴 필요가 없습니다. 예를 들어, 함수 시그니처를 변경한 후 어디서 오류가 발생하는지 즉시 확인할 수 있습니다.

cargo check는 cargo build보다 최대 5배 빠를 수 있습니다. cargo test는 코드의 정확성을 보장하는 핵심 도구입니다.

Rust는 테스트를 일급 시민으로 취급하여, 테스트 작성이 매우 쉽고 자연스럽습니다. 함수에 #[test] 속성만 붙이면 자동으로 테스트로 인식됩니다.

이 두 명령의 핵심 차이는 목적입니다. check는 "컴파일 가능한가?"를 확인하고, test는 "의도대로 작동하는가?"를 확인합니다.

개발 워크플로우는 "코드 작성 → check → 수정 → build → test"의 순서로 진행됩니다.

코드 예제

# 컴파일 오류만 빠르게 체크 (실행 파일 생성 안 함)
cargo check

# 모든 테스트 실행
cargo test

# 특정 테스트만 실행 (이름으로 필터링)
cargo test test_name

# 테스트 출력을 모두 표시 (println! 출력 포함)
cargo test -- --nocapture

# 간단한 테스트 예시
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

설명

이것이 하는 일: 이 명령들은 코드의 정확성을 다양한 측면에서 검증합니다. 첫 번째로, cargo check는 컴파일러를 실행하지만 코드 생성 단계를 건너뜁니다.

타입 검사, 빌림 검사(borrow checker), 라이프타임 검증 등 Rust의 모든 안전성 검사를 수행하지만, 최종 기계어 코드를 만들지 않습니다. 이 덕분에 시간이 크게 단축되며, 특히 대형 프로젝트에서 효과가 큽니다.

IDE나 에디터에서 "저장 시 자동 검사" 기능은 대부분 cargo check를 사용합니다. 그 다음으로, cargo test는 프로젝트 내의 모든 테스트 함수를 찾아서 실행합니다.

Rust 컴파일러는 #[test] 속성이 붙은 함수를 특별하게 처리하여, cargo test 실행 시에만 컴파일되고 실행됩니다. 테스트는 병렬로 실행되어 속도가 빠르며, 각 테스트가 독립적으로 실행되어 서로 영향을 주지 않습니다.

테스트 코드는 보통 #[cfg(test)] 모듈 안에 작성합니다. cfg(test)는 "테스트 빌드일 때만 포함하라"는 의미로, 프로덕션 빌드에서는 테스트 코드가 완전히 제거됩니다.

이렇게 하면 최종 바이너리 크기가 불필요하게 커지지 않습니다. assert_eq!

매크로는 가장 기본적인 테스트 도구입니다. 두 값이 같은지 비교하고, 다르면 테스트가 실패하며 상세한 오류 메시지를 출력합니다.

assert!, assert_ne! 같은 다양한 어설션 매크로가 제공되며, 실패 시 어떤 값이 예상되었고 실제로는 무엇이었는지 자동으로 표시됩니다.

--nocapture 플래그는 디버깅할 때 매우 유용합니다. 기본적으로 Cargo는 성공한 테스트의 출력을 숨기는데, 이 플래그를 사용하면 모든 println!

출력을 볼 수 있습니다. 테스트가 실패하는 이유를 추적할 때 중간 값을 출력해보는 것이 큰 도움이 됩니다.

여러분이 cargo check를 자주 사용하면 오류를 조기에 발견할 수 있고, cargo test로 자동화된 테스트를 작성하면 리팩토링이나 기능 추가 시 기존 기능이 깨지지 않았는지 확신할 수 있습니다. 이것이 안정적인 소프트웨어의 기초입니다.

실전 팁

💡 VSCode의 rust-analyzer는 파일을 저장할 때마다 자동으로 cargo check를 실행하여 실시간 오류 표시를 제공합니다

💡 cargo test -- --test-threads=1로 테스트를 순차적으로 실행할 수 있어, 데이터베이스 같은 공유 리소스 테스트에 유용합니다

💡 통합 테스트는 tests/ 디렉토리에, 단위 테스트는 src/ 파일 내부에 작성하는 것이 Rust 관례입니다

💡 #[ignore] 속성으로 느린 테스트를 기본 실행에서 제외하고, cargo test -- --ignored로 선택적으로 실행할 수 있습니다

💡 cargo test --doc으로 문서 주석 내의 코드 예제도 테스트할 수 있어, 문서가 항상 최신 상태임을 보장합니다


7. 프로젝트 구조 이해하기 - src와 target 디렉토리

시작하며

여러분이 Cargo 프로젝트를 열었을 때 여러 디렉토리와 파일들이 보입니다. 어떤 파일은 직접 수정해야 하고, 어떤 파일은 자동 생성되어 건드리면 안 됩니다.

이 구분을 명확히 이해하지 못하면 불필요한 파일을 Git에 커밋하거나 중요한 파일을 실수로 삭제할 수 있습니다. Rust 프로젝트는 명확한 규칙을 따르는 디렉토리 구조를 가집니다.

소스 코드는 src/에, 빌드 결과물은 target/에, 설정은 루트의 Cargo.toml에 위치합니다. 이 구조는 모든 Rust 프로젝트에서 동일하여 익숙해지면 어떤 프로젝트든 빠르게 파악할 수 있습니다.

이 카드에서는 각 디렉토리와 파일의 역할을 명확히 이해하고, 어떤 것을 버전 관리에 포함해야 하는지 배웁니다.

개요

간단히 말해서, src/는 여러분이 작성하는 소스 코드가 들어가는 곳이고, target/는 Cargo가 생성하는 빌드 결과물이 저장되는 곳입니다. 이 구조가 중요한 이유는 명확한 분리를 통해 프로젝트 관리가 쉬워지기 때문입니다.

소스 코드만 Git에 커밋하고, 빌드 결과물은 제외하는 것이 모든 프로젝트의 기본 원칙입니다. 예를 들어, target/ 디렉토리는 수 GB까지 커질 수 있지만 언제든 cargo build로 재생성할 수 있으므로 버전 관리 대상이 아닙니다.

기존에 src/, build/, bin/, lib/ 같이 복잡한 디렉토리 구조를 가진 C++ 프로젝트와 달리, Rust는 단순하고 일관된 구조를 유지합니다. 어떤 Rust 프로젝트를 열어도 main.rs나 lib.rs를 src/에서 찾으면 됩니다.

Cargo 프로젝트의 핵심 구성 요소는 다섯 가지입니다. src/main.rs는 바이너리의 진입점, src/lib.rs는 라이브러리의 루트, Cargo.toml은 설정 파일, Cargo.lock은 의존성 버전 고정 파일, target/은 빌드 산출물 디렉토리입니다.

이 중 버전 관리에 포함해야 하는 것은 src/, Cargo.toml, 그리고 바이너리 프로젝트가 아닌 경우 Cargo.lock입니다.

코드 예제

# 표준 Rust 프로젝트 구조
my_project/
├── Cargo.toml          # 프로젝트 설정 (버전 관리 O)
├── Cargo.lock          # 의존성 버전 고정 (라이브러리는 버전 관리 X)
├── src/
│   ├── main.rs         # 바이너리 진입점 (버전 관리 O)
│   ├── lib.rs          # 라이브러리 루트 (있는 경우)
│   └── modules/        # 추가 모듈들
│       └── helper.rs
├── tests/              # 통합 테스트 (버전 관리 O)
│   └── integration_test.rs
├── examples/           # 예제 코드 (버전 관리 O)
│   └── example1.rs
└── target/             # 빌드 결과물 (버전 관리 X - .gitignore)
    ├── debug/          # 디버그 빌드
    └── release/        # 릴리스 빌드

설명

이것이 하는 일: Cargo 프로젝트는 명확히 정의된 디렉토리 구조를 통해 코드와 빌드 산출물을 분리합니다. 첫 번째로, src/ 디렉토리는 모든 소스 코드의 홈입니다.

main.rs는 실행 가능한 바이너리 프로젝트의 진입점이며, fn main() 함수를 포함해야 합니다. lib.rs는 라이브러리 프로젝트의 루트로, 다른 프로젝트에서 사용할 공개 API를 정의합니다.

하나의 프로젝트가 main.rs와 lib.rs를 동시에 가질 수도 있습니다. src/ 안에 추가 모듈을 만들 수 있습니다.

src/helper.rs 파일이나 src/helper/ 디렉토리를 만들면, main.rs나 lib.rs에서 mod helper;로 불러올 수 있습니다. 이렇게 코드를 논리적으로 분리하여 가독성과 유지보수성을 높입니다.

그 다음으로, target/ 디렉토리는 Cargo가 완전히 관리하는 영역입니다. 절대로 직접 수정해서는 안 되며, 필요하면 cargo clean으로 전체를 삭제하고 재생성합니다.

target/debug/에는 개발용 빌드가, target/release/에는 최적화된 릴리스 빌드가 저장됩니다. 중간 오브젝트 파일, 의존성 빌드 결과물도 모두 여기에 포함됩니다.

Cargo.lock 파일은 자동 생성되지만 중요한 역할을 합니다. 프로젝트를 처음 빌드할 때 Cargo는 Cargo.toml의 의존성 버전 요구사항을 만족하는 정확한 버전을 선택하고, 이를 Cargo.lock에 기록합니다.

이후 빌드에서는 항상 동일한 버전을 사용하여 "내 컴퓨터에서는 되는데" 문제를 방지합니다. 바이너리 프로젝트는 Cargo.lock을 커밋해야 하지만, 라이브러리 프로젝트는 제외하는 것이 관례입니다.

tests/ 디렉토리는 통합 테스트를 위한 공간입니다. 여기의 파일들은 별도의 바이너리로 컴파일되어 라이브러리의 공개 API만 사용하므로, 실제 사용자 관점에서 테스트할 수 있습니다.

examples/ 디렉토리는 라이브러리 사용법을 보여주는 예제 코드를 담으며, cargo run --example 예제명으로 실행할 수 있습니다. 여러분이 이 구조를 이해하면 프로젝트 내비게이션이 빨라지고, 어떤 파일을 수정하고 어떤 파일을 무시해야 하는지 명확해집니다.

또한 다른 사람의 Rust 프로젝트를 볼 때도 즉시 구조를 파악할 수 있습니다.

실전 팁

💡 .gitignore에 /target/을 추가하여 빌드 결과물이 Git에 포함되지 않도록 하세요 (cargo new가 자동으로 생성)

💡 cargo clean으로 target/ 디렉토리를 삭제하여 디스크 공간을 확보할 수 있으며, 다음 빌드 시 자동으로 재생성됩니다

💡 프로젝트 구조가 복잡해지면 src/bin/ 디렉토리에 여러 바이너리를 만들 수 있으며, 각각 cargo run --bin 이름으로 실행합니다

💡 Cargo.lock을 커밋해야 할지는 프로젝트 타입에 따라 다릅니다: 바이너리/애플리케이션은 커밋하고, 라이브러리는 제외합니다

💡 benches/ 디렉토리를 만들면 벤치마크 코드를 분리할 수 있으며, cargo bench로 성능 측정을 자동화할 수 있습니다


8. 의존성 추가하기 - crates.io 활용

시작하며

여러분이 JSON 파싱, HTTP 요청, 암호화 같은 기능이 필요할 때 모든 것을 처음부터 구현하시나요? 물론 아닙니다.

이미 잘 만들어진 라이브러리를 사용하는 것이 현명합니다. 바퀴를 재발명할 필요는 없으니까요.

Rust 생태계에는 crates.io라는 중앙 패키지 저장소가 있으며, 수만 개의 고품질 라이브러리(crate)가 등록되어 있습니다. JSON 처리부터 웹 프레임워크, 데이터베이스 드라이버, 머신러닝 도구까지 거의 모든 것이 있습니다.

Cargo를 통해 이러한 라이브러리를 단 몇 초 만에 프로젝트에 추가하고 사용할 수 있습니다. Cargo.toml에 한 줄만 추가하면 자동으로 다운로드되고 빌드되어 즉시 사용 가능합니다.

개요

간단히 말해서, crates.io는 Rust의 공식 패키지 저장소이며, Cargo.toml의 [dependencies] 섹션에 원하는 크레이트를 추가하여 프로젝트에 통합할 수 있습니다. 의존성 관리가 중요한 이유는 현대 소프트웨어 개발이 협업과 재사용을 기반으로 하기 때문입니다.

검증된 라이브러리를 사용하면 개발 속도가 빨라지고, 버그가 줄어들며, 보안도 향상됩니다. 예를 들어, serde 라이브러리는 수백만 프로젝트에서 사용되며 수년간 검증되어 직접 JSON 파서를 만드는 것보다 훨씬 안전하고 빠릅니다.

기존에 수동으로 라이브러리를 다운로드하고 빌드 경로를 설정하던 방식과 달리, Cargo는 모든 것을 자동화합니다. 버전 호환성 체크, 의존성의 의존성 해결, 컴파일 순서 최적화까지 모두 처리됩니다.

의존성 추가의 핵심은 세 단계입니다. 첫째, crates.io에서 필요한 크레이트를 검색합니다.

둘째, Cargo.toml의 [dependencies]에 크레이트명과 버전을 추가합니다. 셋째, cargo build를 실행하면 자동으로 다운로드되고 컴파일됩니다.

이후 코드에서 use 문으로 가져와 사용하면 됩니다.

코드 예제

# Cargo.toml에 의존성 추가
[dependencies]
serde = "1.0"                    # JSON 직렬화
serde_json = "1.0"               # JSON 파싱
tokio = { version = "1.0", features = ["full"] }  # 비동기 런타임
reqwest = "0.11"                 # HTTP 클라이언트

# 코드에서 사용하기 (src/main.rs)
use serde::{Serialize, Deserialize};
use serde_json;

#[derive(Serialize, Deserialize)]
struct User {
    name: String,
    age: u32,
}

fn main() {
    let user = User { name: "Alice".to_string(), age: 30 };
    let json = serde_json::to_string(&user).unwrap();
    println!("{}", json);  // {"name":"Alice","age":30}
}

설명

이것이 하는 일: Cargo는 선언적 의존성 관리를 통해 외부 라이브러리를 프로젝트에 자동으로 통합합니다. 첫 번째로, crates.io 웹사이트에서 필요한 기능을 검색합니다.

예를 들어 "json"을 검색하면 serde_json이 가장 많이 사용되는 크레이트로 나타납니다. 각 크레이트 페이지에는 사용법, 버전 히스토리, 의존성, 다운로드 수, GitHub 링크 등이 표시됩니다.

다운로드 수와 최근 업데이트 날짜를 확인하여 활발히 유지보수되는지 판단할 수 있습니다. 그 다음으로, Cargo.toml에 의존성을 추가합니다.

serde = "1.0"은 "1.0 이상 2.0 미만의 serde를 사용하라"는 의미입니다. Cargo는 시맨틱 버저닝 규칙에 따라 호환 가능한 최신 버전(예: 1.0.195)을 자동으로 선택합니다.

중괄호 문법 { version = "1.0", features = ["full"] }은 추가 옵션을 지정할 때 사용하며, features는 선택적 기능을 활성화합니다. Cargo.toml을 저장한 후 cargo build를 실행하면 마법이 일어납니다.

Cargo는 crates.io에서 지정된 크레이트를 다운로드하고, 그 크레이트가 의존하는 다른 크레이트도 재귀적으로 다운로드합니다. 모든 의존성은 ~/.cargo/ 디렉토리에 캐시되어 다른 프로젝트에서도 재사용됩니다.

컴파일 순서를 자동으로 계산하여 의존성을 먼저 빌드한 후 여러분의 코드를 빌드합니다. 코드에서 사용할 때는 use 문으로 가져옵니다.

use serde::{Serialize, Deserialize};는 serde 크레이트에서 두 가지 트레이트를 가져온다는 의미입니다. #[derive(Serialize, Deserialize)]는 구조체에 직렬화 기능을 자동으로 구현하는 매크로로, 이것이 Rust의 강력한 메타프로그래밍 기능입니다.

실제 코드 예시를 보면 User 구조체를 JSON 문자열로 변환하는 것이 단 한 줄입니다. serde_json::to_string()은 Rust의 타입 시스템과 통합되어 타입 안전성을 보장하며, 컴파일 타임에 직렬화 코드가 생성되어 런타임 오버헤드가 거의 없습니다.

여러분이 의존성을 추가할 때는 항상 문서를 먼저 읽어보세요. docs.rs에서 모든 공개 크레이트의 자동 생성된 문서를 볼 수 있으며, 예제 코드와 API 레퍼런스가 포함되어 있습니다.

좋은 크레이트는 명확한 문서와 풍부한 예제를 제공합니다.

실전 팁

💡 cargo add serde 명령으로 Cargo.toml을 직접 편집하지 않고 의존성을 추가할 수 있습니다 (Cargo 1.62+)

💡 cargo tree 명령으로 의존성 트리를 시각화하여 어떤 크레이트가 어떤 것에 의존하는지 파악할 수 있습니다

💡 features를 최소한으로 지정하면 컴파일 시간이 크게 단축됩니다 - 예: tokio = { version = "1", features = ["rt-multi-thread", "net"] }

💡 cargo update 명령으로 Cargo.lock을 업데이트하여 호환 가능한 최신 버전으로 의존성을 업그레이드할 수 있습니다

💡 보안 취약점을 확인하려면 cargo-audit 도구를 설치하여 cargo audit을 실행하면 알려진 보안 문제를 체크할 수 있습니다


9. 에디션 이해하기 - Rust 2021 Edition

시작하며

여러분이 오래된 Rust 코드를 볼 때 낯선 문법을 발견한 적이 있나요? 또는 새로운 Rust 기능을 사용하려고 했는데 컴파일 오류가 발생했나요?

이것은 Rust의 "에디션(Edition)" 시스템 때문일 수 있습니다. 대부분의 프로그래밍 언어는 시간이 지나면서 진화합니다.

새로운 기능이 추가되고, 때로는 하위 호환성이 깨지기도 합니다. Python 2에서 3로의 고통스러운 마이그레이션을 기억하시나요?

Rust는 이 문제를 "에디션" 시스템으로 해결합니다. Rust 에디션은 언어의 특정 스냅샷으로, 2015, 2018, 2021이 있습니다.

중요한 점은 서로 다른 에디션의 코드가 같은 프로젝트에서 공존할 수 있다는 것입니다. 이를 통해 점진적 마이그레이션이 가능하며, 생태계 분열을 방지합니다.

개요

간단히 말해서, Rust 에디션은 3년마다 출시되는 언어 기능과 문법의 번들로, Cargo.toml의 edition 필드로 지정하며, 서로 다른 에디션 코드가 완벽하게 호환됩니다. 에디션이 중요한 이유는 언어의 진화와 안정성을 동시에 달성하기 때문입니다.

새로운 키워드를 추가하거나 문법을 변경해도 기존 코드는 영향을 받지 않습니다. 예를 들어, Rust 2018에서 async와 await가 키워드가 되었지만, Rust 2015 에디션 코드에서는 여전히 변수명으로 async를 사용할 수 있습니다.

기존의 언어 버전 시스템(Python 2 vs 3, PHP 5 vs 7)과 달리, Rust 에디션은 컴파일러 수준에서 통합됩니다. 2015 에디션 라이브러리를 2021 에디션 프로젝트에서 사용해도 아무 문제가 없습니다.

이것이 Rust 생태계의 핵심 강점입니다. 각 에디션의 주요 변화를 보면, 2015는 초기 안정 버전, 2018은 모듈 시스템 개선과 비동기 프로그래밍 기반 마련, 2021은 디폴트 에디션으로 Cargo 해석기 개선과 Rust 언어의 현대화를 포함합니다.

새 프로젝트는 항상 최신 에디션을 사용해야 하며, 기존 프로젝트는 필요할 때 마이그레이션할 수 있습니다.

코드 예제

# Cargo.toml에서 에디션 지정
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"    # 2015, 2018, 2021 중 선택

# Rust 2021의 주요 기능 예시
fn main() {
    // 2021: 디폴트 Cargo 기능 해석기 개선
    // 2021: array IntoIterator 구현
    let arr = [1, 2, 3];
    for x in arr {  // 2021에서 간단하게 사용 가능
        println!("{}", x);
    }

    // 2021: 클로저 캡처 규칙 개선
    let message = String::from("Hello");
    let closure = || println!("{}", message.len());  // 더 효율적
    closure();
}

설명

이것이 하는 일: 에디션 시스템은 언어 진화와 하위 호환성을 동시에 보장하는 Rust만의 독특한 메커니즘입니다. 첫 번째로, edition 필드는 컴파일러에게 어떤 언어 기능과 문법을 활성화할지 알려줍니다.

edition = "2021"을 지정하면 Rust 2021 에디션의 모든 기능을 사용할 수 있으며, 이전 에디션에서는 컴파일 오류가 발생했을 코드도 작동합니다. 예를 들어, 2021에서는 배열을 for 루프에서 직접 사용할 수 있지만 2018에서는 &arr이나 arr.iter()가 필요했습니다.

그 다음으로, 각 에디션은 특정 개선 사항을 번들로 제공합니다. Rust 2018의 가장 큰 변화는 모듈 시스템 개선으로, extern crate를 대부분의 경우 생략할 수 있게 되었습니다.

또한 async/await 문법의 기반이 마련되어 비동기 프로그래밍이 훨씬 쉬워졌습니다. Rust 2021 에디션은 여러 인체공학적 개선을 포함합니다.

클로저 캡처 규칙이 개선되어 불필요한 변수 전체를 캡처하지 않고 실제로 사용하는 필드만 캡처합니다. 이는 메모리 효율성과 안전성을 높입니다.

Cargo의 기능 해석기도 개선되어 의존성 간 기능 충돌이 적어졌습니다. 에디션 간 호환성은 Rust의 핵심 원칙입니다.

서로 다른 에디션으로 작성된 크레이트들이 동일한 프로젝트에서 완벽하게 작동합니다. 컴파일러는 각 크레이트의 에디션을 추적하고, 크레이트 경계에서 필요한 변환을 자동으로 수행합니다.

이것이 Python 2/3 같은 생태계 분열을 방지합니다. 마이그레이션은 cargo fix --edition 명령으로 자동화됩니다.

이 명령은 코드를 분석하여 새 에디션에 맞게 자동으로 수정하며, 대부분의 경우 수동 작업 없이 완료됩니다. 예를 들어, 새로운 키워드와 충돌하는 식별자는 자동으로 원시 식별자(r#async)로 변환됩니다.

여러분이 새 프로젝트를 시작할 때는 항상 최신 에디션(2021)을 사용하세요. 기존 프로젝트는 급할 필요 없이 준비되었을 때 마이그레이션하면 됩니다.

에디션 업그레이드는 의무가 아니며, 오래된 에디션도 계속 지원됩니다.

실전 팁

💡 cargo new로 생성하는 모든 프로젝트는 자동으로 최신 에디션을 사용하므로 수동 설정이 필요 없습니다

💡 cargo fix --edition으로 에디션 마이그레이션을 시도하기 전에 반드시 Git 커밋을 만들어 변경 사항을 추적하세요

💡 Rust Edition Guide(https://doc.rust-lang.org/edition-guide/)에서 각 에디션의 변경 사항과 마이그레이션 가이드를 확인할 수 있습니다

💡 라이브러리 개발자라면 가능한 낮은 에디션을 유지하여 더 많은 프로젝트에서 사용할 수 있도록 하는 것이 좋습니다

💡 에디션은 3년 주기로 출시되므로 다음 에디션인 Rust 2024를 미리 준비할 수 있습니다


10. 첫 실전 프로젝트 - Hello User 인터렉티브 프로그램

시작하며

여러분이 지금까지 배운 모든 지식을 종합하여 실제로 작동하는 프로그램을 만들어볼 시간입니다. 단순히 "Hello, world!"를 출력하는 것을 넘어서, 사용자와 상호작용하는 프로그램을 작성해봅시다.

사용자 입력을 받고 처리하는 것은 대부분의 프로그램에서 필요한 기본 기능입니다. 명령줄 도구든, 서버든, GUI 애플리케이션이든 사용자와의 상호작용이 핵심입니다.

이 카드에서는 사용자의 이름을 입력받아 개인화된 인사를 출력하는 간단한 프로그램을 만듭니다. 이 과정에서 표준 입력, 문자열 처리, 오류 처리 등 실무에서 필수적인 기술을 배우게 됩니다.

개요

간단히 말해서, Hello User 프로그램은 사용자의 이름을 입력받아 "Hello, [이름]!" 형태로 인사하는 인터렉티브 프로그램입니다. 이 프로그램이 중요한 이유는 실제 프로그래밍의 기본 요소들을 모두 다루기 때문입니다.

표준 입력(stdin)에서 데이터를 읽고, 문자열을 처리하며, 결과를 출력하는 것은 거의 모든 프로그램에서 필요합니다. 예를 들어, 설정 파일을 읽거나, 네트워크에서 데이터를 받거나, 데이터베이스 쿼리 결과를 처리할 때 이와 유사한 패턴을 사용합니다.

기존의 정적인 Hello World와 달리, 이 프로그램은 동적으로 작동합니다. 실행할 때마다 다른 결과를 만들어내며, 사용자 경험이 향상됩니다.

이것이 실용적인 프로그램의 시작점입니다. 이 프로그램의 핵심 구성 요소는 세 가지입니다.

std::io::stdin()으로 표준 입력 핸들을 얻고, String::new()로 입력을 저장할 문자열 버퍼를 만들며, read_line()으로 한 줄을 읽습니다. trim()으로 공백과 개행 문자를 제거하여 깔끔한 결과를 얻을 수 있습니다.

코드 예제

// src/main.rs - 인터렉티브 Hello User 프로그램
use std::io;  // 표준 입출력 모듈

fn main() {
    // 사용자에게 프롬프트 표시
    println!("What is your name?");

    // 빈 문자열 버퍼 생성 (가변)
    let mut name = String::new();

    // 표준 입력에서 한 줄 읽기
    io::stdin()
        .read_line(&mut name)
        .expect("Failed to read line");

    // 개행 문자 제거
    let name = name.trim();

    // 개인화된 인사
    println!("Hello, {}! Welcome to Rust programming.", name);
}

설명

이것이 하는 일: 이 프로그램은 표준 입력에서 텍스트를 읽고 처리하여 사용자와 상호작용합니다. 첫 번째로, use std::io;는 표준 라이브러리의 입출력 모듈을 가져옵니다.

std는 모든 Rust 프로그램에 자동으로 포함되는 표준 라이브러리이며, io 모듈은 입출력 기능을 제공합니다. 이 선언 없이도 std::io::stdin()처럼 전체 경로를 사용할 수 있지만, use 문으로 간결하게 만들 수 있습니다.

그 다음으로, String::new()는 힙에 할당된 빈 문자열을 생성합니다. mut 키워드가 중요한데, Rust에서 변수는 기본적으로 불변(immutable)이므로 명시적으로 가변(mutable)으로 선언해야 합니다.

read_line()은 문자열의 내용을 수정하므로 mut가 필수입니다. io::stdin()은 표준 입력에 대한 핸들을 반환합니다.

read_line(&mut name)은 사용자가 Enter를 누를 때까지 입력을 기다리고, 입력된 내용을 name 문자열에 추가합니다. &mut는 가변 참조로, 함수가 문자열의 내용을 수정할 수 있도록 허용합니다.

Rust의 소유권 시스템 덕분에 이 과정에서 메모리 안전성이 보장됩니다. expect() 메서드는 오류 처리를 담당합니다.

read_line()은 Result<usize, Error> 타입을 반환하는데, 이는 성공(Ok)이거나 오류(Err)일 수 있습니다. expect()는 오류가 발생하면 프로그램을 종료하고 지정된 메시지를 출력합니다.

실무에서는 더 정교한 오류 처리를 사용하지만, 간단한 프로그램에서는 expect()가 충분합니다. trim() 메서드는 문자열 양 끝의 공백, 탭, 개행 문자(\n, \r\n)를 제거합니다.

사용자가 Enter를 누르면 개행 문자가 입력에 포함되므로, trim()으로 제거해야 깔끔한 결과를 얻을 수 있습니다. let name = name.trim();은 이전 name을 가리는(shadowing) 새로운 변수를 만드는데, 이는 Rust에서 일반적인 패턴입니다.

println!의 {} 플레이스홀더는 변수의 값을 삽입합니다. 여러 플레이스홀더를 사용할 수 있으며, println!("Hello, {}, you are {} years old", name, age)처럼 여러 값을 한 번에 포맷할 수 있습니다.

이것이 Rust의 강력한 문자열 포맷팅 시스템입니다. 여러분이 이 프로그램을 실행하면 실제로 상호작용하는 경험을 하게 됩니다.

이것이 정적 예제와 실제 프로그램의 차이입니다. 여기서 배운 입력 처리 패턴은 파일 읽기, 네트워크 통신, 데이터베이스 작업 등 모든 I/O 작업의 기초가 됩니다.

실전 팁

💡 read_line()은 개행 문자를 포함하므로 항상 trim()을 사용하여 제거하는 것이 좋습니다

💡 실무에서는 expect() 대신 match나 ? 연산자로 오류를 더 우아하게 처리하는 것이 권장됩니다

💡 print!("Enter name: "); io::stdout().flush().unwrap(); 패턴으로 개행 없는 프롬프트를 만들 수 있습니다

💡 여러 줄 입력을 받으려면 loop와 read_line()을 조합하여 빈 줄이 나올 때까지 읽을 수 있습니다

💡 UTF-8이 아닌 입력을 처리해야 한다면 read_line() 대신 read_to_end()와 String::from_utf8_lossy()를 고려하세요


#Rust#HelloWorld#Cargo#ProjectManagement#BuildSystem#프로그래밍언어

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.