Booil Jung

Rust 프로그래밍 언어 종합 자습서

Rust는 ‘안전성’, ‘속도’, ‘동시성’이라는 세 가지 핵심 목표를 가지고 설계된 현대적인 시스템 프로그래밍 언어다.1 이 언어의 근본적인 철학은 C++와 같은 저수준 언어의 성능과 제어 능력을 제공하면서도, 해당 언어들이 고질적으로 안고 있던 메모리 관련 버그의 위험을 원천적으로 제거하는 데 있다. Rust의 가장 독특하고 강력한 특징은 가비지 컬렉터(Garbage Collector, GC) 없이 컴파일 시점에 메모리 안전성을 보장한다는 점이다.1

전통적으로 프로그래밍 언어 설계에서 고수준의 추상화(개발자의 편의성)와 저수준의 제어(하드웨어에 대한 직접적인 통제와 성능)는 양립하기 어려운 가치로 여겨져 왔다.4 GC를 사용하는 언어들(Java, Python, Go 등)은 메모리 관리를 자동화하여 개발자의 부담을 줄이고 메모리 누수나 이중 해제 같은 오류를 방지하지만, GC가 작동하는 동안 발생하는 예측 불가능한 성능 저하(pause)를 감수해야 한다. 반면, C/C++와 같이 수동으로 메모리를 관리하는 언어는 최고의 성능을 낼 수 있지만, 개발자의 작은 실수가 댕글링 포인터(dangling pointer), 버퍼 오버런(buffer overrun)과 같은 심각한 보안 취약점으로 이어질 수 있다.

Rust는 이러한 트레이드오프에 도전한다. Rust는 ‘소유권(Ownership)’이라는 독창적인 시스템을 통해 컴파일러가 프로그램의 모든 메모리 사용을 컴파일 시점에 분석하고 검증하도록 한다. 이 과정을 통해 메모리 관련 버그가 실행 파일에 포함될 가능성을 원천적으로 차단하면서도, 런타임에 GC와 같은 추가적인 오버헤드를 발생시키지 않는다. 이러한 접근법을 통해 Rust는 ‘제로 코스트 추상화(Zero-Cost Abstraction)’ 원칙을 실현한다. 즉, 개발자가 고수준의 편리한 기능을 사용해 코드를 작성하더라도, 그 결과물은 직접 저수준으로 세밀하게 작성한 코드와 동일한 성능을 내도록 보장하는 것이다.5

결론적으로 Rust는 시스템 프로그래밍, 임베디드 시스템, 고성능 웹 서버, 게임 엔진과 같이 성능이 매우 중요하면서도 안정성과 보안이 타협될 수 없는 영역에서 C/C++를 대체할 수 있는 강력한 대안으로 자리매김하고 있다. 이는 단순한 기능의 집합이 아니라, 성능과 안전성이 공존할 수 있다는 새로운 프로그래밍 패러다임을 제시하는 것이다.

Rust의 역사는 2006년 모질라(Mozilla) 소속 개발자였던 그레이던 호어(Graydon Hoare)의 개인 프로젝트로 시작되었다.1 이후 그 잠재력을 인정받아 모질라 재단의 공식 연구 프로젝트로 편입되었고, 차세대 웹 브라우저 엔진인 서보(Servo)를 개발하는 데 핵심 언어로 채택되면서 본격적으로 발전하기 시작했다. 2010년 7월에 처음으로 대중에게 공개되었으며, 수년간의 개발과 커뮤니티의 피드백을 거쳐 2015년 5월 15일에 안정 버전인 1.0이 정식으로 출시되었다.1

Rust의 발전 과정에서 중요한 전환점은 2021년 2월, 독립적인 비영리 조직인 ‘Rust 재단(Rust Foundation)’의 설립이다.1 이는 Rust가 특정 기업에 종속되지 않고, 개방적이고 중립적인 거버넌스 아래에서 지속적으로 발전할 수 있는 토대를 마련한 사건이다. AWS, Google, Huawei, Microsoft, Mozilla가 창립 멤버로 참여했으며, 이는 Rust가 단순한 커뮤니티 프로젝트를 넘어 주요 테크 기업들이 신뢰하고 투자하는 산업 표준 기술로 인정받고 있음을 명확히 보여준다.1

Rust 생태계는 기술적 우수성뿐만 아니라 독특하고 활발한 커뮤니티 문화로도 잘 알려져 있다. Rust의 공식 마스코트는 밝은 주황색 게 모양의 ‘페리스(Ferris)’로, Rust 관련 커뮤니티와 자료에서 친근한 상징으로 널리 사용된다.1 이 때문에 Rust 개발자들은 스스로를 ‘러스타시안(Rustacean)’이라는 애칭으로 부르며 강한 유대감을 형성하고 있다. 이러한 커뮤니티는 새로운 학습자들이 질문하고 성장할 수 있도록 돕는 따뜻하고 환영하는 분위기를 가지고 있어, 언어의 가파른 학습 곡선을 극복하는 데 큰 도움이 된다.4

Rust는 그 설계 철학 덕분에 이론적인 언어에 머무르지 않고, 실제 산업 현장의 다양한 문제를 해결하는 데 적극적으로 채택되고 있다. 시스템 프로그래밍부터 웹 개발, 클라우드 인프라, 게임 개발에 이르기까지 그 활용 범위는 매우 넓다.6

시스템 프로그래밍 분야에서 Rust는 운영체제, 임베디드 기기, 디바이스 드라이버 개발에 사용된다. 메모리 안전성을 컴파일 시점에 보장하므로, 안정성이 최우선인 저수준 환경에 이상적이다. Microsoft는 Windows 커널의 일부를 Rust로 재작성하는 프로젝트를 진행하고 있으며, 이는 Rust가 가장 중요한 시스템 소프트웨어에도 적용될 수 있음을 입증하는 사례다.3

웹 및 클라우드 인프라 분야에서 Rust의 채택은 특히 두드러진다.

웹 개발 분야에서는 특히 WebAssembly(Wasm)와의 시너지를 통해 프론트엔드에서도 주목받고 있다. Next.js와 같은 JavaScript 프레임워크는 내부 컴파일 및 번들링 도구를 Rust로 재작성하여 개발 속도를 극적으로 향상시켰다. 또한, Figma와 같은 웹 기반 디자인 도구는 기존 TypeScript 서버를 Rust로 재작성하여 비약적인 성능 향상을 이루었다.8

이 외에도 CLI(Command-Line Interface) 도구 (예: ripgrep), 게임 개발 (Bevy 엔진), 블록체인 등 성능과 안정성이 동시에 요구되는 거의 모든 분야에서 Rust의 영향력이 빠르게 확대되고 있다.6 이러한 구체적인 사례들은 Rust가 단순한 유행이 아니라, 현대 소프트웨어 개발의 복잡한 문제들을 해결할 수 있는 실질적인 해결책임을 증명한다.

Rust 개발 환경을 구축하는 가장 표준적이고 권장되는 방법은 rustup이라는 공식 툴체인 설치 관리자를 사용하는 것이다. rustup은 Rust 컴파일러(rustc), 패키지 매니저(cargo), 표준 라이브러리 등을 설치하고, 여러 버전의 Rust 툴체인을 손쉽게 관리할 수 있도록 돕는다.9

설치가 성공적으로 완료되었는지 확인하기 위해 새 터미널이나 명령 프롬프트 창을 열고 다음 명령어를 실행한다.

rustc --version
cargo --version

각 명령어는 설치된 Rust 컴파일러와 Cargo의 버전 정보를 출력해야 한다.9

Rust 툴체인을 최신 안정 버전으로 업데이트하려면 다음 명령어를 사용한다.

rustup update

Rust를 시스템에서 완전히 제거하려면 다음 명령어를 실행한다.10

rustup self uninstall

이제 Rust를 설치했으니, 가장 기본적인 프로그램을 작성하고 실행해 본다. 이 과정은 rustc 컴파일러를 직접 사용하여 Rust 코드의 컴파일 및 실행 흐름을 이해하는 데 목적이 있다.

  1. 프로젝트 디렉토리 생성: 원하는 위치에 프로젝트를 저장할 디렉토리를 만든다.

    mkdir ~/projects
    cd ~/projects
    mkdir hello_world
    cd hello_world
    
  2. 소스 파일 작성: main.rs라는 이름의 파일을 생성하고 다음 코드를 입력한다. Rust 소스 파일은 관례적으로 .rs 확장자를 사용한다.14

    // 파일명: main.rs
    fn main() {
        println!("Hello, world!");
    }
    

    이 코드는 main이라는 이름의 함수를 정의한다. main 함수는 모든 Rust 실행 프로그램의 시작점이다. 함수 본문 안의 println!은 화면에 텍스트를 출력하는 Rust 매크로다. println 뒤에 붙은 !는 이것이 일반 함수가 아닌 매크로 호출임을 나타낸다.14

  3. 컴파일 및 실행: 터미널에서 다음 명령어를 사용하여 소스 코드를 컴파일한다.

    rustc main.rs
    

    이 명령어는 main.rs 파일을 컴파일하여 실행 가능한 바이너리 파일을 생성한다. Linux와 macOS에서는 main, Windows에서는 main.exe라는 이름의 파일이 생성된다.14

    생성된 실행 파일을 실행하려면 다음 명령어를 입력한다.

    # Linux 및 macOS
    ./main
    # Windows
    .\main.exe
    

터미널에 “Hello, world!”가 성공적으로 출력되면 첫 Rust 프로그램을 완성한 것이다.14

앞선 방식처럼 rustc를 직접 사용하는 것은 간단한 프로그램에는 적합하지만, 프로젝트가 복잡해지고 외부 라이브러리를 사용하게 되면 매우 번거로워진다. Cargo는 Rust의 공식 빌드 시스템이자 패키지 매니저로, 프로젝트 생성, 코드 빌드, 의존성 관리, 테스트 실행 등 개발에 필요한 거의 모든 작업을 자동화해 준다.3 Rust 개발의 표준 방식은 Cargo를 사용하는 것이다.

  1. 새 프로젝트 생성: cargo new 명령어를 사용하여 새로운 Cargo 프로젝트를 생성한다.

    # projects 디렉토리로 이동
    cd ~/projects
    cargo new hello_cargo
    

    이 명령어는 hello_cargo라는 이름의 디렉토리를 생성하고, 그 안에 표준 프로젝트 구조를 자동으로 만들어준다.16

    • Cargo.toml: 프로젝트의 설정 파일. 프로젝트 이름, 버전, 저자 정보 및 외부 라이브러리 의존성 등을 기술한다.
    • src/main.rs: 소스 코드가 위치하는 디렉토리와 기본 “Hello, world!” 코드가 포함된 파일.
  2. 프로젝트 빌드 및 실행: 생성된 프로젝트 디렉토리로 이동하여 Cargo 명령어를 사용한다.

    • 빌드: cargo build 명령어는 소스 코드를 컴파일하여 target/debug/ 디렉토리 안에 실행 파일을 생성한다.16

      cd hello_cargo
      cargo build
      
    • 실행: cargo run 명령어는 코드를 컴파일(아직 빌드되지 않았다면)하고 그 결과물을 즉시 실행한다. 개발 과정에서 가장 빈번하게 사용되는 명령어다.18

      cargo run
      
    • 빠른 검사: cargo check 명령어는 실행 파일을 생성하지 않고 코드의 컴파일 가능 여부만 빠르게 확인한다. 복잡한 프로젝트에서 전체 컴파일을 기다리지 않고 코드의 문법적 오류를 신속하게 확인하고 싶을 때 매우 유용하다.21

      cargo check
      

Cargo는 단순한 편의 도구를 넘어 Rust 개발 경험의 핵심이다. 프로젝트 구조를 표준화하고, 의존성 관리를 단순화하며, 빌드 과정을 일관되게 만들어 개발자가 언어의 본질적인 로직에 더 집중할 수 있도록 돕는다. 이는 Rust의 다소 가파른 학습 곡선을 완화하고, 특히 여러 개발자가 협업하는 팀 환경에서 생산성을 극대화하는 중요한 요소로 작용한다.4 앞으로의 모든 예제는 Cargo 프로젝트 내에서 진행하는 것을 기본으로 한다.

Rust에서 변수는 let 키워드를 사용하여 선언한다. Rust의 핵심적인 안전성 철학 중 하나는 ‘기본적 불변성(immutable by default)’이다. 이는 한번 값이 할당된 변수는 기본적으로 그 값을 변경할 수 없음을 의미한다.23

fn main() {
    let x = 5;
    println!("x의 값: {}", x);
    // 다음 줄의 주석을 해제하면 컴파일 오류가 발생한다.
    // x = 6; // error[E0384]: cannot assign twice to immutable variable `x`
    // println!("변경된 x의 값: {}", x);
}

위 코드에서 x에 새로운 값을 할당하려고 하면 컴파일러는 오류를 발생시킨다. 이때 Rust 컴파일러는 매우 친절하게 오류의 원인과 해결책(help: make this binding mutable: 'mut x')을 제시해 준다.24 이처럼 의도치 않은 값의 변경을 컴파일 시점에 막아 프로그램의 안정성을 높이는 것이 불변성을 기본으로 하는 이유다.

변수의 값을 변경해야 할 필요가 있을 때는 mut(mutable의 약자) 키워드를 사용하여 명시적으로 가변성을 선언해야 한다.23

fn main() {
    let mut y = 10;
    println!("y의 초기값: {}", y);
    y = y + 1;
    println!("변경된 y의 값: {}", y); // 정상적으로 11이 출력된다.
}

Rust에는 변수 재할당과 비슷해 보이지만 근본적으로 다른 ‘섀도잉’이라는 개념이 있다. let 키워드를 사용하여 이전에 선언된 변수와 같은 이름의 새로운 변수를 선언하는 것이다. 이 새로운 변수는 이전 변수를 ‘가리게(shadow)’ 되며, 해당 스코프 내에서는 새로운 변수만 유효하다.24

fn main() {
    let x = 5;
    let x = x + 1; // 첫 번째 x를 가리고 새로운 x를 선언

    {
        let x = x * 2; // 안쪽 스코프에서 바깥쪽 x를 가리고 새로운 x를 선언
        println!("안쪽 스코프의 x 값: {}", x); // 12 출력
    }

    println!("바깥쪽 스코프의 x 값: {}", x); // 6 출력
}

섀도잉은 mut를 사용한 변수 재할당과 두 가지 중요한 차이점이 있다. 첫째, 섀도잉은 let 키워드를 다시 사용하므로 변수의 타입을 바꿀 수 있다. 둘째, 섀도잉된 변수 자체는 여전히 불변이다. 이는 값의 변환이 필요할 때 임시 변수 이름을 만들 필요 없이 같은 이름을 재사용하여 코드의 가독성을 높일 수 있게 해준다.

상수는 const 키워드로 선언하며, 프로그램이 실행되는 동안 절대 변하지 않는 값을 위해 사용된다. 상수에는 몇 가지 규칙이 있다.23

const MAX_POINTS: u32 = 100_000;

Rust는 정적 타입 언어(statically typed language)로, 모든 변수의 타입이 컴파일 시점에 결정되어야 한다. 이는 타입 불일치로 인한 런타임 오류를 방지하여 프로그램의 안정성을 높인다. 하지만 개발자가 모든 변수의 타입을 명시적으로 선언할 필요는 없다. 컴파일러가 할당되는 값을 기반으로 타입을 추론(type inference)하는 강력한 기능을 갖추고 있기 때문이다.25

Rust의 데이터 타입은 크게 스칼라(Scalar) 타입과 컴파운드(Compound) 타입으로 나뉜다.28

스칼라 타입은 단일 값을 표현한다. Rust에는 네 가지 기본 스칼라 타입이 있다.

분류 타입 크기 (바이트) 값의 범위
부호 있는 정수 i8 1 -128 ~ 127
  i16 2 -32,768 ~ 32,767
  i32 4 -2,147,483,648 ~ 2,147,483,647
  i64 8 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
  i128 16 -1.70e+38 ~ 1.70e+38 (근사치)
부호 없는 정수 u8 1 0 ~ 255
  u16 2 0 ~ 65,535
  u32 4 0 ~ 4,294,967,295
  u64 8 0 ~ 18,446,744,073,709,551,615
  u128 16 0 ~ 3.40e+38 (근사치)
아키텍처 기반 정수 isize / usize 4 또는 8 아키텍처에 따라 다름
부동소수점 f32 4  
  f64 8  
불리언 bool 1 true, false
문자 char 4 U+0000 ~ U+D7FF, U+E000 ~ U+10FFFF

컴파운드 타입은 여러 값을 하나의 타입으로 묶는다. Rust에는 두 가지 기본 컴파운드 타입이 있다.

함수는 Rust 코드의 기본적인 구성 단위다. fn 키워드를 사용하여 함수를 정의하며, 함수와 변수의 이름은 관례적으로 스네이크 케이스(snake_case)를 따른다.31

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

fn another_function(x: i32) { // 매개변수는 반드시 타입을 명시해야 한다.
    println!("x의 값: {}", x);
}

Rust는 표현식 기반(expression-based) 언어라는 중요한 특징을 가진다. 이를 이해하는 것은 Rust 코드를 자연스럽게 작성하는 데 필수적이다.34

이러한 구분은 함수 반환 값 처리 방식에 큰 영향을 미친다.

함수가 값을 반환할 경우, 화살표(->) 뒤에 반환 타입을 명시해야 한다. Rust에서는 return 키워드를 사용하여 명시적으로 값을 반환할 수도 있지만, 함수 본문의 마지막 표현식(세미콜론이 없는)이 자동으로 함수의 반환 값이 되는 것이 더 관용적인 스타일이다.31

fn five() -> i32 {
    5 // 세미콜론이 없으므로 이 표현식의 값인 5가 반환된다.
}

fn plus_one(x: i32) -> i32 {
    x + 1 // 세미콜론이 없다. x + 1의 결과가 반환된다.
}

fn main() {
    let x = five();
    let y = plus_one(x);
    println!("y의 값: {}", y); // 6 출력
}

코드를 설명하고 가독성을 높이기 위해 주석을 사용한다. Rust는 두 가지 종류의 기본 주석을 지원한다.36

또한, Rust에는 cargo doc 명령어로 문서를 생성하는 데 사용되는 특별한 문서화 주석(/////!)이 있다. 이는 라이브러리를 만들 때 매우 유용하다.39

Rust의 가장 핵심적이고 독창적인 기능은 ‘소유권(Ownership)’ 시스템이다. 이 시스템은 가비지 컬렉터 없이도 메모리 안전성을 보장하는 Rust의 핵심 원리다. 소유권, 대여(Borrowing), 그리고 라이프타임(Lifetime)은 분리된 개념이 아니라, ‘댕글링 포인터(dangling pointer)’와 ‘데이터 경쟁(data race)’이라는 두 가지 고질적인 메모리 버그를 컴파일 타임에 원천적으로 차단하기 위해 유기적으로 설계된 하나의 시스템이다. 이 시스템의 규칙을 이해하는 것이 Rust를 마스터하는 첫걸음이다.45

소유권 시스템은 모든 프로그램이 따라야 하는 몇 가지 간단한 규칙으로 이루어져 있다. 컴파일러(정확히는 ‘빌림 검사기’, borrow checker)는 이 규칙들이 위반되지 않았는지 컴파일 시점에 검사한다.46

소유권의 세 가지 규칙:

  1. Rust의 모든 값은 ‘소유자(owner)’라고 불리는 변수를 갖는다.
  2. 한 번에 단 하나의 소유자만 존재할 수 있다.
  3. 소유자가 스코프(scope)를 벗어나면, 그 값은 버려진다(dropped).

이 규칙들을 이해하기 위해 스택(Stack)과 힙(Heap) 메모리 모델을 간단히 살펴보자. i32와 같이 컴파일 시점에 크기가 고정된 타입은 빠르고 간단한 스택 메모리에 저장된다. 반면, String과 같이 사용 중에 크기가 변할 수 있는 데이터는 힙 메모리에 할당된다. 힙 할당은 더 유연하지만, 운영체제에 메모리를 요청하고 반납하는 과정이 필요하기 때문에 상대적으로 비용이 높다. 소유권 시스템은 바로 이 힙 메모리를 안전하고 효율적으로 관리하기 위해 존재한다.45

힙 데이터를 사용하는 변수가 다른 변수에 할당될 때, Rust는 데이터를 복사하는 대신 소유권을 ‘이동(move)’시킨다.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1의 소유권이 s2로 이동(move)된다.

    // println!("{}, world!", s1); // 컴파일 오류! s1은 더 이상 유효하지 않다.
}

위 코드에서 s1s2에 할당된 후, s1은 더 이상 힙에 있는 “hello” 문자열 데이터의 소유자가 아니다. 소유권은 s2로 완전히 넘어갔으며, s1은 무효화된다. 만약 s1을 다시 사용하려고 하면 컴파일러는 “borrow of moved value” 오류를 발생시킨다. 이는 하나의 데이터에 대해 두 변수가 동시에 소유권을 주장하여 발생할 수 있는 ‘이중 해제(double free)’ 오류를 원천적으로 방지하기 위함이다.46

반면, i32와 같이 전체 데이터가 스택에 저장되는 타입의 경우, 다른 변수에 할당될 때 값이 단순히 복사된다. 이러한 타입들은 Copy 트레이트(trait)를 구현하고 있다.

fn main() {
    let x = 5;
    let y = x; // x의 값이 y로 복사된다.

    println!("x = {}, y = {}", x, y); // x와 y 모두 유효하다.
}

이 경우 x는 소유권 이동 후에도 무효화되지 않고 계속 사용할 수 있다. 모든 기본 스칼라 타입(정수, 부동소수점, 불리언, 문자)과 이들로만 구성된 튜플은 Copy 타입이다.45

힙에 저장된 데이터를 명시적으로 깊게 복사하고 싶다면, clone() 메서드를 사용해야 한다. clone()은 잠재적으로 비용이 많이 드는 작업이므로, 코드 상에 명시적으로 드러나게 하여 개발자가 성능에 미치는 영향을 인지하도록 한다.45

함수에 값을 전달하면서 소유권을 계속 유지하고 싶을 때가 있다. 이럴 때 사용하는 것이 ‘참조(reference)’다. 참조는 소유권을 이동시키는 대신, 값에 접근할 수 있는 권한을 ‘빌려주는(borrowing)’ 것이다. 참조는 앰퍼샌드(&) 기호를 사용하여 만든다.50

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // s1의 참조를 전달한다.
    println!("'{}'의 길이는 {}입니다.", s1, len); // s1은 여전히 유효하다.
}

fn calculate_length(s: &String) -> usize { // &String은 String의 참조를 받는다.
    s.len()
} // 여기서 s는 스코프를 벗어나지만, 참조하는 데이터의 소유자가 아니므로 아무 일도 일어나지 않는다.

calculate_length 함수는 String의 소유권을 갖는 대신, 그 참조를 매개변수로 받는다. 따라서 함수가 종료되어도 s1의 소유권은 main 함수에 그대로 남아있어 계속 사용할 수 있다.

참조에도 변수와 마찬가지로 불변과 가변이 있다.

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Rust의 안전성을 보장하는 핵심은 바로 이 대여 규칙에 있다. 컴파일러는 다음 규칙을 강제하여 컴파일 시점에 데이터 경쟁(data race)을 방지한다.51

특정 스코프 내에서, 특정 데이터에 대해 다음 중 하나만 허용된다:

  1. 여러 개의 불변 참조 (&T)
  2. 단 하나의 가변 참조 (&mut T)

즉, 누군가 데이터를 변경할 수 있는 가능성(&mut T)이 있다면, 다른 누구도 그 데이터에 접근(읽기조차도)할 수 없어야 한다. 반대로, 여러 명이 데이터를 읽고(&T) 있다면, 그 누구도 데이터를 변경할 수 없어야 한다. 이 규칙 덕분에 여러 스레드가 동시에 데이터에 접근하더라도 안전성이 보장된다.

슬라이스는 소유권을 갖지 않는 또 다른 종류의 참조다. 슬라이스는 String, 배열, 벡터와 같은 컬렉션의 연속된 일부분을 참조할 수 있게 해준다.55

문자열 슬라이스는 String의 일부를 가리키는 참조다. [시작_인덱스..끝_인덱스] 형태의 범위 문법을 사용하여 생성한다. 시작 인덱스는 포함되지만, 끝 인덱스는 포함되지 않는다.56

fn main() {
    let s = String::from("hello world");

    let hello = &s[0..5]; // 또는 &s[..5]
    let world = &s[6..11]; // 또는 &s[6..]

    println!("{}, {}", hello, world);
}

범위의 시작이 0이면 생략할 수 있고([..5]), 끝이 문자열의 마지막이면 생략할 수 있다([6..]). 전체 문자열을 슬라이스로 만들려면 [..]를 사용한다.

지금까지 자주 보았던 문자열 리터럴(let s = "hello";)의 타입은 사실 &'static str이다. 즉, 프로그램의 바이너리 데이터 영역에 저장된 문자열 데이터를 가리키는, 정적 생명주기를 가진 슬라이스다. 이것이 문자열 리터럴이 불변인 이유다.56

슬라이스 개념은 문자열에만 국한되지 않는다. 배열이나 벡터와 같은 다른 컬렉션에도 동일하게 적용될 수 있다.56

fn main() {
    let a = ;
    let slice = &a[1..3]; // 타입은 &[i32]

    assert_eq!(slice, &);
}

슬라이스는 내부적으로 실제 데이터를 가리키는 포인터와 슬라이스의 길이를 함께 저장한다. 이는 소유권 이전 없이도 데이터의 일부에 안전하고 효율적으로 접근할 수 있는 강력한 방법을 제공한다. 예를 들어, String에 대한 가변 참조와 해당 String의 슬라이스(불변 참조)가 동시에 존재할 수 없도록 컴파일러가 막아주기 때문에, 데이터가 변경되어 슬라이스가 무효화되는 것과 같은 버그를 예방할 수 있다.56

구조체(struct)는 여러 연관된 데이터를 하나의 의미 있는 단위로 묶어 새로운 커스텀 타입을 정의하는 데 사용된다. 이는 다른 언어의 객체나 레코드와 유사한 개념이다.59

struct 키워드와 구조체의 이름을 사용하여 정의하고, 중괄호 {} 안에 각 데이터 조각의 이름과 타입을 정의한다. 이 데이터 조각을 ‘필드(field)’라고 부른다.61

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // 구조체 인스턴스 생성
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // 점(.) 표기법으로 필드에 접근
    user1.email = String::from("anotheremail@example.com");
}

구조체의 인스턴스를 생성할 때는 구조체이름 { 필드명: 값,... } 구문을 사용한다. 인스턴스가 가변(mut)으로 선언된 경우, 점 표기법을 사용하여 필드의 값을 변경할 수 있다. Rust에서는 필드별로 가변성을 지정할 수 없고, 인스턴스 전체가 가변이거나 불변이어야 한다.61

함수의 매개변수 이름과 구조체 필드 이름이 같을 때는 ‘필드 초기화 축약(field init shorthand)’ 구문을 사용하여 코드를 간결하게 만들 수 있다.59

fn build_user(email: String, username: String) -> User {
    User {
        email, // email: email, 과 동일
        username, // username: username, 과 동일
        active: true,
        sign_in_count: 1,
    }
}

또한, ‘구조체 갱신(struct update)’ 문법(..)을 사용하면 기존 인스턴스의 나머지 필드 값을 사용하여 새로운 인스턴스를 만들 수 있다.59

// user1 인스턴스가 있다고 가정
let user2 = User {
    email: String::from("another@example.com"),
   ..user1 // email을 제외한 나머지 필드는 user1의 값을 사용
};

구조체와 관련된 동작(함수)은 impl(implementation의 약자) 블록 안에 정의한다. impl 블록 안에 정의된 함수를 ‘메서드(method)’라고 부른다.63

메서드의 첫 번째 매개변수는 항상 self이며, 이는 메서드가 호출되는 구조체 인스턴스를 나타낸다. self는 세 가지 형태로 사용될 수 있다.

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // &self를 사용하는 메서드
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }

    // self를 매개변수로 갖지 않는 연관 함수(associated function)
    fn square(size: u32) -> Self {
        Self { width: size, height: size }
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let sq = Rectangle::square(25); // 연관 함수는 :: 문법으로 호출

    println!("rect1의 면적: {}", rect1.area()); // 메서드는. 문법으로 호출
    println!("rect1은 rect2를 포함할 수 있는가? {}", rect1.can_hold(&rect2));
}

self를 첫 번째 매개변수로 갖지 않는 함수도 impl 블록 안에 정의할 수 있는데, 이를 ‘연관 함수’라고 한다. 연관 함수는 구조체의 인스턴스가 없어도 호출할 수 있으며, 주로 String::from이나 위 예제의 Rectangle::square처럼 생성자 역할을 하는 함수를 만들 때 사용된다.63

열거형(enum)은 어떤 값이 여러 가능한 종류(배리언트, variant) 중 하나일 수 있음을 표현하는 타입이다. 다른 언어의 열거형이 주로 상수의 집합으로 사용되는 것과 달리, Rust의 열거형은 각 배리언트가 서로 다른 타입과 양의 데이터를 가질 수 있다는 점에서 훨씬 강력하다.64

enum Message {
    Quit, // 연관된 데이터 없음
    Move { x: i32, y: i32 }, // 이름 있는 필드를 가진 구조체 형태
    Write(String), // String 하나를 포함
    ChangeColor(i32, i32, i32), // 세 개의 i32 값을 포함
}

// 열거형에도 impl 블록을 사용하여 메서드를 정의할 수 있다.
impl Message {
    fn call(&self) {
        //...
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

Message 열거형은 네 가지 다른 종류의 메시지를 하나의 타입으로 표현한다. 이는 관련된 데이터 구조들을 하나의 타입으로 묶어주어 코드를 더 체계적으로 관리할 수 있게 한다.66

match는 열거형의 값을 처리하는 데 가장 이상적인 도구다. match는 주어진 값을 여러 패턴과 비교하여, 일치하는 패턴에 해당하는 코드 블록을 실행한다. match의 가장 중요한 특징은 ‘철저함(exhaustiveness)’이다. 즉, match 표현식은 반드시 모든 가능한 경우를 처리해야만 한다. 컴파일러는 모든 배리언트가 처리되었는지 확인하고, 누락된 경우가 있다면 컴파일 오류를 발생시킨다. 이는 버그를 방지하는 매우 강력한 안전장치다.66

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("행운의 페니!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("{:?} 주의 쿼터 동전!", state);
            25
        }
    }
}

Rust는 다른 많은 언어에 존재하는 null 값을 사용하지 않는다. null은 “값이 없음”을 나타내지만, 종종 null인 값을 실제 값처럼 사용하려다 발생하는 런타임 오류(null pointer exception)의 주된 원인이 된다.

대신 Rust는 Option<T>라는 표준 라이브러리 열거형을 사용하여 값이 있을 수도 있고 없을 수도 있는 상황을 표현한다.66

enum Option<T> {
    Some(T), // 값이 존재하며, T 타입의 값을 감싸고 있음
    None,    // 값이 없음
}

Option<T>를 사용하면 컴파일러가 값이 None일 경우를 처리하도록 강제할 수 있다. Option<T> 값을 사용하려면 반드시 Some의 경우와 None의 경우를 모두 처리해야 하므로, null 관련 버그를 컴파일 시점에 예방할 수 있다.

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

match는 모든 경우를 처리해야 하지만, 때로는 단 하나의 패턴에만 관심이 있고 나머지 경우는 무시하고 싶을 때가 있다. 이럴 때 if let 구문을 사용하면 match보다 더 간결하게 코드를 작성할 수 있다.66

let config_max = Some(3u8);

// match를 사용하는 경우
match config_max {
    Some(max) => println!("최대값은 {}로 설정되었습니다.", max),
    _ => (), // 나머지 경우는 아무것도 하지 않음
}

// if let을 사용하는 경우 (더 간결함)
if let Some(max) = config_max {
    println!("최대값은 {}로 설정되었습니다.", max);
}

if letmatch의 문법적 설탕(syntactic sugar)으로, 하나의 패턴만 검사하고 그 외의 경우는 무시한다. 필요에 따라 else 블록을 추가할 수도 있다.

벡터(Vec<T>)는 동일한 타입의 값들을 여러 개 저장할 수 있는 가변 크기의 리스트다. 데이터는 메모리 상에 연속적으로 배치된다. 고정된 크기를 갖는 배열과 달리, 벡터는 실행 중에 요소를 추가하거나 제거하면서 크기를 동적으로 변경할 수 있다.69

Vec::new() 함수를 사용하여 빈 벡터를 생성할 수 있다. 이 경우, 어떤 타입의 요소를 저장할지 명시해야 한다. 더 편리한 방법은 vec! 매크로를 사용하여 초기값을 포함한 벡터를 생성하는 것이다. 이 경우 컴파일러가 타입을 자동으로 추론한다.70

fn main() {
    // 타입을 명시하여 빈 벡터 생성
    let v1: Vec<i32> = Vec::new();

    // vec! 매크로를 사용하여 초기값을 포함한 벡터 생성
    let v2 = vec!; // 컴파일러가 Vec<i32>로 추론
}

벡터에 요소를 추가하려면 push 메서드를 사용한다. 벡터를 수정해야 하므로 mut 키워드로 가변으로 선언해야 한다.72

fn main() {
    let mut v = Vec::new();
    v.push(5);
    v.push(6);
    v.push(7);
}

벡터도 다른 구조체와 마찬가지로 소유권 규칙을 따른다. 벡터 변수가 스코프를 벗어나면 벡터와 그 안의 모든 요소들은 메모리에서 해제된다(drop).70

벡터의 요소에 접근하는 방법은 두 가지가 있다.70

  1. 인덱싱 문법 (``): &v와 같이 인덱스를 사용하여 요소의 참조를 얻는다. 만약 존재하지 않는 인덱스에 접근하려고 하면 프로그램은 패닉(panic)을 일으키며 즉시 종료된다.
  2. get 메서드: v.get(2)와 같이 get 메서드를 사용한다. 이 메서드는 Option<&T>를 반환한다. 인덱스가 유효하면 Some(&element)를, 유효하지 않으면 None을 반환한다. 이 방법은 패닉 없이 안전하게 요소에 접근할 수 있게 해준다.
fn main() {
    let v = vec!;

    let third: &i32 = &v;
    println!("세 번째 원소: {}", third);

    match v.get(2) {
        Some(third) => println!("세 번째 원소: {}", third),
        None => println!("세 번째 원소가 없습니다."),
    }
}

for 루프를 사용하여 벡터의 모든 요소를 안전하게 순회할 수 있다.

fn main() {
    let v = vec!;
    // 불변 참조로 순회
    for i in &v {
        println!("{}", i);
    }

    let mut v_mut = vec!;
    // 가변 참조로 순회하며 값 변경
    for i in &mut v_mut {
        *i += 50; // 역참조 연산자(*)를 사용하여 값을 변경
    }
}

가변 참조로 순회할 때는 역참조 연산자(*)를 사용하여 참조가 가리키는 실제 값에 접근하여 수정해야 한다.73

Rust는 문자열을 다루기 위해 두 가지 주요 타입을 제공한다: String&str(문자열 슬라이스). 이 둘의 차이점을 이해하는 것은 Rust의 소유권 시스템을 이해하는 데 매우 중요하다.73

String::new()로 빈 문자열을 만들거나, to_string() 메서드 또는 String::from() 함수를 사용하여 &str로부터 String을 생성할 수 있다.75

fn main() {
    let mut s1 = String::new();
    let data = "초기 내용";
    let s2 = data.to_string();
    let s3 = String::from("초기 내용");
}

Stringpush_str 메서드로 &str을, push 메서드로 단일 char를 추가하여 수정할 수 있다.75

fn main() {
    let mut s = String::from("foo");
    s.push_str("bar");
    s.push('!');
    println!("{}", s); // "foobar!" 출력
}

+ 연산자나 format! 매크로를 사용하여 여러 문자열을 합칠 수도 있다. + 연산자는 첫 번째 String의 소유권을 가져가므로 주의해야 한다. format! 매크로는 소유권을 가져가지 않으면서 여러 값을 포맷팅하여 새로운 String을 생성하므로 더 편리하고 효율적일 때가 많다.75

fn main() {
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // s1의 소유권은 이동했고, 더 이상 사용할 수 없다.

    let t1 = String::from("tic");
    let t2 = String::from("tac");
    let t3 = String::from("toe");
    let t = format!("{}-{}-{}", t1, t2, t3); // 모든 변수의 소유권은 유지된다.
}

Rust의 String은 UTF-8 인코딩을 기반으로 하므로, 문자의 바이트 크기가 가변적일 수 있다. 예를 들어, ‘a’는 1바이트지만, ‘ㄱ’이나 ‘😊’는 3~4바이트를 차지한다. 이러한 이유로 String은 정수 인덱싱(s[i])을 지원하지 않는다. 잘못된 바이트 경계를 참조하여 유효하지 않은 문자를 생성하는 것을 컴파일 시점에 방지하기 위함이다.

대신, 문자열을 다룰 때는 명시적인 방법을 사용해야 한다.

fn main() {
    for c in "안녕하세요".chars() {
        println!("{}", c);
    }

    for b in "Hello".bytes() {
        println!("{}", b);
    }
}

해시 맵(HashMap<K, V>)은 키(K 타입)와 값(V 타입)을 쌍으로 저장하는 컬렉션이다. 해시 함수를 사용하여 키를 값에 매핑하므로, 평균적으로 매우 빠른 데이터 조회가 가능하다. 벡터와 마찬가지로 힙에 데이터를 저장한다.80

해시 맵을 사용하려면 먼저 std::collections::HashMap을 스코프로 가져와야 한다. HashMap::new()로 빈 해시 맵을 생성하고, insert 메서드로 키와 값을 추가한다.80

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("블루"), 10);
    scores.insert(String::from("옐로"), 50);
}

get 메서드에 키를 전달하여 값을 가져올 수 있다. getOption<&V>를 반환하므로, 키가 존재하지 않는 경우를 안전하게 처리할 수 있다. for 루프를 사용하여 해시 맵의 모든 키-값 쌍을 순회할 수도 있다.82

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("블루"), 10);

    let team_name = String::from("블루");
    let score = scores.get(&team_name).copied().unwrap_or(0);
    println!("점수: {}", score);

    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
}

i32와 같이 Copy 트레이트를 구현하는 타입의 값은 해시 맵에 복사된다. 하지만 String과 같이 소유권을 가진 타입의 값은 해시 맵으로 소유권이 이동(move)된다. insert 후에 원래 변수를 사용하려고 하면 컴파일 오류가 발생한다.82

해시 맵에 이미 존재하는 키에 대해 insert를 호출하면 기존 값은 새로운 값으로 덮어쓰인다.83

때로는 키가 존재하지 않을 때만 값을 삽입하고 싶을 수 있다. 이때는 entry API와 or_insert 메서드를 사용하면 편리하다. entry는 해당 키의 존재 여부를 나타내는 Entry 열거형을 반환하고, or_insert는 키가 존재하지 않을 경우에만 주어진 값을 삽입한다.81

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("블루"), 10);

    // "블루" 키는 이미 존재하므로 아무 일도 일어나지 않는다.
    scores.entry(String::from("블루")).or_insert(50);
    // "옐로" 키는 없으므로 삽입된다.
    scores.entry(String::from("옐로")).or_insert(50);

    println!("{:?}", scores); // {"블루": 10, "옐로": 50}
}

Rust는 프로그램 실행 중에 발생할 수 있는 오류를 처리하기 위해 예외(exception)를 사용하지 않는다. 대신, 오류를 두 가지 주요 범주로 나누어 처리한다: 복구 가능한 오류와 복구 불가능한 오류.84

복구 불가능한 오류는 프로그램이 더 이상 안전하게 실행될 수 없는 심각한 상태를 의미한다. 예를 들어, 배열의 범위를 벗어나는 인덱스에 접근하려는 시도와 같은 버그가 여기에 해당한다. 이런 상황에서 Rust는 panic! 매크로를 호출하여 프로그램을 즉시 중단시킨다.84

panic!이 호출되면 프로그램은 기본적으로 다음 두 가지 작업을 수행한다.

  1. 스택 되감기(Unwinding): 현재 스레드의 스택을 거슬러 올라가면서 각 함수가 남긴 데이터를 정리한다. 이는 상대적으로 작업량이 많지만, 프로그램을 예측 가능한 상태로 남겨둔다.
  2. 프로그램 종료: 정리 작업이 끝나면 프로그램이 종료된다.

개발자가 직접 panic!을 호출하여 특정 조건에서 프로그램을 강제 종료시킬 수도 있다.

fn main() {
    panic!("crash and burn");
}

스택 되감기 과정이 부담스러운 경우, Cargo.toml 파일에 설정을 추가하여 panic! 발생 시 즉시 프로그램을 중단(abort)하도록 할 수 있다. 이 경우 운영체제가 메모리를 정리하게 된다.84

```Ini, TOML [profile.release] panic = ‘abort’



대부분의 오류는 프로그램 전체를 중단시킬 만큼 심각하지 않다. 예를 들어, 파일을 열려고 시도했지만 파일이 존재하지 않는 경우는 흔히 발생할 수 있으며, 이는 오류를 호출자에게 알려주어 다른 조치를 취하도록 하는 것이 합리적이다. 이러한 복구 가능한 오류를 처리하기 위해 Rust는 `Result<T, E>` 열거형을 사용한다.86

`Result<T, E>`는 다음과 같이 정의된다.

```Rust
enum Result<T, E> {
    Ok(T),   // 작업이 성공했음을 나타내며, 성공 결과값 T를 포함한다.
    Err(E),  // 작업이 실패했음을 나타내며, 오류 정보 E를 포함한다.
}

Result를 반환하는 함수는 성공 또는 실패의 가능성을 함수의 시그니처에 명시적으로 표현한다. 이를 통해 함수를 사용하는 개발자는 오류 처리 로직을 반드시 작성하도록 강제받으며, 잠재적인 오류를 놓치는 실수를 방지할 수 있다.

Result 값을 처리하는 가장 기본적인 방법은 match 표현식을 사용하는 것이다.

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("파일을 열 수 없습니다: {:?}", error);
        }
    };
}

위 코드는 File::open이 반환하는 Resultmatch로 처리한다. 성공(Ok)하면 파일 핸들을 f에 할당하고, 실패(Err)하면 panic!을 호출한다.

ResultOk일 것이라고 확신하는 경우, unwrap이나 expect 메서드를 사용하여 간단하게 값을 추출할 수 있다.

use std::fs::File;

fn main() {
    // unwrap 사용
    // let f = File::open("hello.txt").unwrap();

    // expect 사용 (더 나은 오류 메시지 제공)
    let f = File::open("hello.txt").expect("hello.txt 파일을 열 수 없습니다.");
}

함수 내에서 발생한 오류를 직접 처리하지 않고, 함수를 호출한 쪽으로 다시 전달하고 싶을 때가 있다. 이를 오류 전파라고 한다. ? 연산자를 사용하면 이 과정을 매우 간결하게 처리할 수 있다.

Result 값을 반환하는 표현식 뒤에 ?를 붙이면, ResultOk일 경우 안의 값을 추출하고, Err일 경우 현재 함수의 실행을 즉시 중단하고 해당 Err 값을 반환한다. ? 연산자는 Result를 반환하는 함수 내에서만 사용할 수 있다.

use std::io;
use std::fs::File;
use std::io::Read;

//? 연산자를 사용하지 않은 경우
fn read_username_from_file_long() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

//? 연산자를 사용하여 간결하게 표현한 경우
fn read_username_from_file_short() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

// 더 나아가 체이닝(chaining)을 통해 한 줄로 표현
fn read_username_from_file_shortest() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

? 연산자는 Rust의 오류 처리를 강력하면서도 장황하지 않게 만들어주는 핵심적인 기능이다.

제네릭은 구체적인 데이터 타입 대신 추상적인 타입을 사용하여 함수, 구조체, 열거형 등을 정의하는 기능이다. 이를 통해 코드 중복을 줄이고, 타입에 구애받지 않는 유연하고 재사용 가능한 코드를 작성할 수 있다.89

예를 들어, i32 슬라이스에서 가장 큰 값을 찾는 함수와 char 슬라이스에서 가장 큰 값을 찾는 함수는 로직이 동일하지만 타입 때문에 별도로 작성해야 한다. 제네릭을 사용하면 이를 하나의 함수로 통합할 수 있다.

타입 매개변수는 꺾쇠괄호 <> 안에 선언하며, 관례적으로 T(Type의 약자)와 같은 대문자를 사용한다.89

// 제네릭을 사용하지 않은 경우
fn largest_i32(list: &[i32]) -> &i32 { /*... */ }
fn largest_char(list: &[char]) -> &char { /*... */ }

// 제네릭을 사용한 경우
fn largest<T>(list: &) -> &T {
    // 이 코드는 아직 컴파일되지 않는다. T 타입이 비교 가능한지 알 수 없기 때문.
    // 이 문제는 '트레이트' 섹션에서 해결한다.
    let mut largest = &list;
    for item in list {
        // if item > largest {... }
    }
    largest
}

구조체나 열거형에서도 제네릭을 사용하여 다양한 타입을 담을 수 있는 데이터 구조를 만들 수 있다.91

// T 타입의 x, y 좌표를 갖는 Point 구조체
struct Point<T> {
    x: T,
    y: T,
}

// 서로 다른 타입을 가질 수 있는 Point 구조체
struct PointMulti<T, U> {
    x: T,
    y: U,
}

fn main() {
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };
    let mixed_point = PointMulti { x: 5, y: 4.0 };
}

앞서 살펴본 Vec<T>, HashMap<K, V>, Option<T>, Result<T, E> 등 표준 라이브러리의 많은 타입들이 제네릭을 효과적으로 사용하고 있다.

Rust의 제네릭은 ‘단형성화(monomorphization)’라는 과정을 통해 컴파일 시점에 구체적인 타입으로 변환된다. 즉, 컴파일러는 제네릭 코드를 사용하는 모든 구체적인 타입에 대해 각각의 버전을 생성한다. 이 덕분에 제네릭을 사용하더라도 런타임 성능 저하가 전혀 발생하지 않는다. 이는 Rust의 ‘제로 코스트 추상화’ 원칙을 보여주는 또 다른 예다.92

트레이트(trait)는 특정 타입이 가질 수 있는 공유 동작(shared behavior)을 정의하는 방법이다. 다른 언어의 인터페이스(interface)와 유사한 개념으로, 여러 타입에 공통적으로 적용될 수 있는 메서드 시그니처의 집합을 정의한다.94

trait 키워드를 사용하여 트레이트를 정의하고, 그 안에 메서드 시그니처를 선언한다. 특정 타입에 대해 트레이트를 구현하려면 impl TraitName for TypeName 구문을 사용하고, 트레이트에 정의된 메서드들을 구체적으로 구현한다.

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

위 예제에서 Summary 트레이트는 summarize라는 공통 동작을 정의하고, NewsArticleTweet은 각각 자신에게 맞는 방식으로 이 동작을 구현한다.

트레이트는 제네릭과 결합하여 강력한 추상화를 제공한다. ‘트레이트 바운드(trait bound)’를 사용하여 제네릭 타입이 특정 트레이트를 구현해야 함을 명시할 수 있다.

Rust

// `item`은 Summary 트레이트를 구현하는 어떤 타입이든 될 수 있다.
pub fn notify(item: &impl Summary) {
    println!("속보! {}", item.summarize());
}

// 위와 동일한 의미를 갖는 더 긴 문법 (트레이트 바운드 문법)
pub fn notify_long<T: Summary>(item: &T) {
    println!("속보! {}", item.summarize());
}

impl Trait 문법이나 <T: Trait> 트레이트 바운드 문법을 사용하면, 함수는 Summary 트레이트를 구현한 NewsArticle 인스턴스와 Tweet 인스턴스를 모두 인자로 받을 수 있다. 이를 통해 타입에 관계없이 일관된 방식으로 동작을 호출할 수 있다.94

트레이트의 메서드에 기본 동작을 제공할 수도 있다. 이렇게 하면 해당 트레이트를 구현하는 타입은 필요에 따라 메서드를 오버라이드할 수 있지만, 필수는 아니다.97

Rust

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(자세히 보기: {}...)", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
    // summarize 메서드는 기본 구현을 사용한다.
}

라이프타임(lifetime)은 모든 참조자가 유효한 범위를 나타내는 개념이다. 대부분의 경우 컴파일러가 라이프타임을 자동으로 추론하고 검사하지만(라이프타임 생략 규칙), 때로는 개발자가 직접 라이프타임을 명시해야 하는 경우가 있다. 라이프타임은 빌림 검사기(borrow checker)가 댕글링 참조(dangling reference), 즉 이미 해제된 메모리를 가리키는 참조가 없음을 보장하기 위해 사용된다.98

라이프타임의 주된 목적은 참조가 참조하는 데이터보다 오래 살아남는 것을 방지하는 것이다.

Rust

// 이 코드는 컴파일되지 않는다.
fn main() {
    let r;
    {
        let x = 5;
        r = &x; // r은 x를 참조한다.
    } // 여기서 x는 스코프를 벗어나 메모리에서 해제된다.
    // println!("r: {}", r); // r은 해제된 메모리를 가리키는 댕글링 참조가 된다.
}

위 코드에서 컴파일러는 x의 라이프타임이 r의 라이프타임보다 짧다는 것을 인지하고 컴파일 오류를 발생시킨다.

함수가 참조를 인자로 받아서 참조를 반환하는 경우, 컴파일러는 입력 참조와 반환 참조의 라이프타임 관계를 알 수 없다. 이때 개발자는 제네릭 라이프타임 매개변수를 사용하여 이 관계를 명시해주어야 한다.

라이프타임 매개변수는 작은따옴표(')로 시작하며, 관례적으로 'a, 'b와 같이 짧은 이름을 사용한다.101

Rust

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

longest 함수의 시그니처는 컴파일러에게 다음과 같이 알려준다: “두 문자열 슬라이스 xy는 최소한 'a라는 동일한 라이프타임 동안 살아있어야 하며, 반환되는 문자열 슬라이스 또한 최소한 'a 라이프타임 동안 유효할 것이다.”

이 명시를 통해 컴파일러는 longest 함수의 반환값이 댕글링 참조가 되지 않음을 보장할 수 있다. 예를 들어, 서로 다른 라이프타임을 가진 두 참조를 이 함수에 전달하면, 반환값의 라이프타임은 둘 중 더 짧은 라이프타임에 맞춰지게 된다.99

라이프타임은 Rust의 안전성 보장에서 매우 중요한 역할을 하지만, 실제로 코드를 변경하는 것이 아니라 컴파일러에게 참조 관계를 설명해주는 주석과 같은 역할을 한다. 이를 통해 런타임 오버헤드 없이 메모리 안전성을 극대화할 수 있다.

스마트 포인터는 일반적인 포인터처럼 동작하지만, 추가적인 메타데이터와 기능을 갖춘 데이터 구조다. Rust에서는 DerefDrop 트레이트를 구현한 구조체를 통해 스마트 포인터를 구현한다. StringVec<T>도 내부적으로 메모리를 관리하고 소유하므로 일종의 스마트 포인터로 볼 수 있다. 여기서는 힙 메모리 관리와 소유권 모델을 더 정교하게 다루기 위한 몇 가지 핵심적인 스마트 포인터를 소개한다.102

Rust의 소유권과 대여 규칙은 “겁 없는 동시성(Fearless Concurrency)”을 가능하게 한다. 많은 동시성 관련 버그, 특히 데이터 경쟁은 컴파일 시점에 발견되고 방지된다. Rust는 동시성 프로그래밍을 위해 여러 도구를 제공한다.

{

let mut num = counter.lock().unwrap();

*num += 1;

});

handles.push(handle);

}

    for handle in handles {
        handle.join().unwrap();
    }

    println!("결과: {}", *counter.lock().unwrap());
}
```

9.3. 외부 크레이트 활용

Rust의 코드 패키지를 ‘크레이트(crate)’라고 부른다. Rust의 강력한 생태계는 crates.io라는 공식 크레이트 레지스트리를 통해 수많은 오픈 소스 라이브러리를 제공한다. Cargo를 사용하면 이러한 외부 크레이트를 프로젝트에 쉽게 통합할 수 있다.111

외부 크레이트를 사용하려면 Cargo.toml 파일의 [dependencies] 섹션에 해당 크레이트의 이름과 버전을 추가하면 된다.

Ini, TOML

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]
rand = "0.8.5" # 난수 생성을 위한 'rand' 크레이트 추가

Cargo.toml에 의존성을 추가한 후 cargo buildcargo run을 실행하면, Cargo는 자동으로 crates.io에서 해당 크레이트와 그 의존성들을 다운로드하고 컴파일한다.

소스 코드에서는 use 키워드를 사용하여 외부 크레이트의 기능을 스코프로 가져와 사용할 수 있다.

Rust

// src/main.rs
use rand::Rng; // rand 크레이트의 Rng 트레이트를 가져온다.

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("비밀 번호: {}", secret_number);
}

cargo add 명령어를 제공하는 cargo-edit과 같은 도구를 사용하면 터미널에서 직접 의존성을 추가하여 Cargo.toml 파일을 수정할 수도 있다.112 이처럼 Cargo와

crates.io는 Rust 개발자가 풍부한 라이브러리 생태계를 손쉽게 활용하여 생산성을 높일 수 있도록 지원하는 핵심적인 도구다.

참고 자료

  1. Rust(프로그래밍 언어) (r702 판) - 나무위키, 8월 17, 2025에 액세스, https://namu.wiki/w/Rust(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%20%EC%96%B8%EC%96%B4)?uuid=a6eed93b-a716-4b64-adfc-0c429d506737
  2. 프로그래밍 언어 Rust의 특징과 장점 - F-Lab, 8월 17, 2025에 액세스, https://f-lab.kr/insight/features-of-rust
  3. Rust를 사용하여 Windows에서 개발하는 방법에 대한 개요, 8월 17, 2025에 액세스, https://learn.microsoft.com/ko-kr/windows/dev-environment/rust/overview
  4. 소개 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch00-00-introduction.html
  5. 4년간의 Rust 사용 후기, 8월 17, 2025에 액세스, https://blog.cro.sh/posts/four-years-of-rust/
  6. Rust 언어: 장단점 및 사용 사례 - Dak.so, 8월 17, 2025에 액세스, https://www.dak.so/rust-tutorials/pros-and-cons
  7. Rust의 실제 사용 사례는 뭐임? - Reddit, 8월 17, 2025에 액세스, https://www.reddit.com/r/rust/comments/123cu43/whats_an_actual_use_case_for_rust/?tl=ko
  8. 러스트로 뭘 할 수 있나요? - 파이썬과 비교하며 배우는 러스트 프로그래밍, 8월 17, 2025에 액세스, https://indosaram.github.io/rust-python-book/ch1-02.html
  9. Rust 설치 및 버전 관리 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/2-Rust-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC
  10. 설치하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch01-01-installation.html
  11. 러스트 설치 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch01-01-installation.html
  12. Windows에서 Rust에 대한 개발 환경 설정 - Microsoft Learn, 8월 17, 2025에 액세스, https://learn.microsoft.com/ko-kr/windows/dev-environment/rust/setup
  13. Rust 개발환경 구축하기 - 프로그래밍 톺아보기, 8월 17, 2025에 액세스, https://comb.tistory.com/3
  14. Hello, World! - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch01-02-hello-world.html
  15. Hello, World! - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch01-02-hello-world.html
  16. Hello World! - [러스트] 러스트 시작하기 - 소소한 개발 공부, 8월 17, 2025에 액세스, https://soso-study.tistory.com/101
  17. Hello, World! - The Rust Programming Language - MIT, 8월 17, 2025에 액세스, https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/second-edition/ch01-02-hello-world.html
  18. Cargo 사용법 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/4-Cargo-%EC%82%AC%EC%9A%A9%EB%B2%95
  19. Rust 아는 척 해보기-2 프로젝트 생성 - 신비한 비, 8월 17, 2025에 액세스, https://rainsister.tistory.com/149
  20. [Rust] cargo 사용하기 - 코딩 Rust - 티스토리, 8월 17, 2025에 액세스, https://baroqcat.tistory.com/m/entry/cargo-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
  21. [Rust] 카고(Cargo)에 대해서 - 데이로깅 - 티스토리, 8월 17, 2025에 액세스, https://luckylucky.tistory.com/12
  22. [Rust] Cargo 이용해 빌드하기 - HardCore in Programming - 티스토리, 8월 17, 2025에 액세스, https://kukuta.tistory.com/416
  23. [Rust] 3. 기본 문법과 데이터 타입 - comnic’s Dev&Life, 8월 17, 2025에 액세스, https://comnic.tistory.com/57
  24. Rust 2장 - 1. 변수와 상수 - 쪼렙전사 - 티스토리, 8월 17, 2025에 액세스, https://chobowarrior.tistory.com/10
  25. [일반 프로그래밍 개념] 데이터 타입 - 2. 컴파운드 타입 - 메모장 - 티스토리, 8월 17, 2025에 액세스, https://ahnanne.tistory.com/73
  26. Rust 변수 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/5-Rust-%EB%B3%80%EC%88%98
  27. Rust 변수,상수, 데이터 타입 - 프로그래밍 톺아보기, 8월 17, 2025에 액세스, https://comb.tistory.com/4
  28. Rust 2장 - 2. 기본 데이터 타입 - 쪼렙전사, 8월 17, 2025에 액세스, https://chobowarrior.tistory.com/11
  29. 데이터 타입들 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch03-02-data-types.html
  30. 데이터 타입 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch03-02-data-types.html
  31. Rust 기본, 함수 (Functions) - 10 하루윤회, 8월 17, 2025에 액세스, https://codeself.com/entry/Rust-%EA%B8%B0%EB%B3%B8-%ED%95%A8%EC%88%98-Functions
  32. Rust 기초 - 함수, 8월 17, 2025에 액세스, https://qphone.tistory.com/7
  33. Rust 함수 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/9-Rust-%ED%95%A8%EC%88%98
  34. 함수 동작 원리 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch03-03-how-functions-work.html
  35. Rust - Functions - velog, 8월 17, 2025에 액세스, https://velog.io/@dunyazad/Rust-Functions
  36. [Rust] 5. 주석 - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/444
  37. 주석 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch03-04-comments.html
  38. 주석, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch03-04-comments.html
  39. 러스트독: 러스트에서 API 문서를 위한 초보자 가이드 - Apidog, 8월 17, 2025에 액세스, https://apidog.com/kr/blog/rustdoc-kr/
  40. [Rust] 6. 제어문과 반복문 (if, loop, for, while) - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/445
  41. [Rust] 제어문 - 극꼼이 이야기 (GG_Tales) - 티스토리, 8월 17, 2025에 액세스, https://geukggom.tistory.com/302
  42. [Rust] 4. 제어 흐름(if, loop, while, for) - comnic’s Dev&Life - 티스토리, 8월 17, 2025에 액세스, https://comnic.tistory.com/58
  43. [Rust] 제어문 If-else, while, do-while - Alive and awake - 티스토리, 8월 17, 2025에 액세스, https://popofly.tistory.com/228
  44. Rust, If 문 및 loop문, control flow (제어문), 8월 17, 2025에 액세스, https://popofly.tistory.com/58
  45. [Rust] 7. 소유권 그리고 스택과 힙 - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/446
  46. 쪼잔한 Rust 4.1. 소유권이 무엇이냐 - 개발새발로그, 8월 17, 2025에 액세스, https://dgkim5360.tistory.com/entry/what-i-learned-from-the-rust-book-chapter-4-1-what-is-ownership
  47. 소유권이 뭔가요? - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch04-01-what-is-ownership.html
  48. 소유권 (Ownership) - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/advanced
  49. 소유권(Ownership)과 원시 타입 - Rust 프로그래밍 - TaeGit - 티스토리, 8월 17, 2025에 액세스, https://taegit.tistory.com/10
  50. [Rust] 참조 (대여) - 코딩 Rust - 티스토리, 8월 17, 2025에 액세스, https://baroqcat.tistory.com/entry/Rust-%EC%B0%B8%EC%A1%B0-%EB%8C%80%EC%97%AC
  51. [Rust]오너십의 참조와 대여. aka 포인터 by Kwoncheol Shin - Medium, 8월 17, 2025에 액세스, https://medium.com/@kwoncharles/rust-%EC%98%A4%EB%84%88%EC%8B%AD-%EC%B0%B8%EC%A1%B0-%EB%8C%80%EC%97%AC-16f825aaa882
  52. 참조자와 빌림 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch04-02-references-and-borrowing.html
  53. 쪼잔한 Rust 4.2. 참조와 대여 - 개발새발로그 - 티스토리, 8월 17, 2025에 액세스, https://dgkim5360.tistory.com/entry/what-i-learned-from-the-rust-book-chapter-4-2-references-and-borrowing
  54. Rust - 참조 변수와 대여 - 프로그래밍노리터, 8월 17, 2025에 액세스, https://plas.tistory.com/135
  55. [Rust] 9. 슬라이스, 8월 17, 2025에 액세스, https://copynull.tistory.com/448
  56. 슬라이스, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch04-03-slices.html
  57. [Rust] 슬라이스(slices) - 극꼼이 이야기 (GG_Tales) - 티스토리, 8월 17, 2025에 액세스, https://geukggom.tistory.com/305
  58. 슬라이스 (Slice) - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/103-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8A%A4-Slice
  59. 구조체 (Struct) - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/13-%EA%B5%AC%EC%A1%B0%EC%B2%B4-Struct
  60. 구조체로 연관된 데이터를 구조화하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch05-00-structs.html
  61. 구조체 정의 및 인스턴트화 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch05-01-defining-structs.html
  62. 구조체를 정의하고 생성하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch05-01-defining-structs.html
  63. 구조체 impl 블럭 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/14-%EA%B5%AC%EC%A1%B0%EC%B2%B4-impl-%EB%B8%94%EB%9F%AD
  64. Rust 기본, 열거형 (Enumerations) - 10 하루윤회, 8월 17, 2025에 액세스, https://codeself.com/entry/Rust-%EA%B8%B0%EB%B3%B8-%EC%97%B4%EA%B1%B0%ED%98%95-Enumerations
  65. [Rust] Start Rust (Day 11) - Enums and Pattern Matching [1] - IT’s Portfolio - 티스토리, 8월 17, 2025에 액세스, https://it-neicebee.tistory.com/154
  66. [Rust] 열거형과 패턴매칭 - YONGJIN LAB, 8월 17, 2025에 액세스, https://yongj.in/rust/rust-enum-and-pattern-match/
  67. 열거형과 패턴 매칭 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch06-00-enums.html
  68. 열거형과 패턴 매칭, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch06-00-enums.html
  69. [Rust] Vector - 코딩 Rust - 티스토리, 8월 17, 2025에 액세스, https://baroqcat.tistory.com/entry/Rust-%EC%9E%85%EB%AC%B8-Vector
  70. 벡터에 여러 값의 목록 저장하기, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch08-01-vectors.html
  71. [Rust] 13. 벡터(Vector) 컬렉션 (컨테이너) - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/452
  72. 벡터 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch08-01-vectors.html
  73. [RUST] 8. 범용 컬렉션 - 코딩 부부 - 티스토리, 8월 17, 2025에 액세스, https://wecandev.tistory.com/161
  74. [Rust] String과 &str - 코딩 Rust - 티스토리, 8월 17, 2025에 액세스, https://baroqcat.tistory.com/entry/Rust-%EC%9E%85%EB%AC%B8-String%EA%B3%BC-str
  75. [Rust] 14. String (문자열) - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/453
  76. [Rust] 문자열 리터럴에서 to_string() vs to_owned() by Seonghun - Medium, 8월 17, 2025에 액세스, https://dev-seonghun.medium.com/rust-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A6%AC%ED%84%B0%EB%9F%B4%EC%97%90%EC%84%9C-to-string-vs-to-owned-f40adc2b7ab5
  77. 문자열 타입 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/8-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%83%80%EC%9E%85
  78. Rust - String - velog, 8월 17, 2025에 액세스, https://velog.io/@dunyazad/Rust-String
  79. 러스트에서 문자열을 효과적으로 연결하기 :: 개똥이야기, 8월 17, 2025에 액세스, https://unipro.tistory.com/243
  80. [Rust/러스트] 해쉬맵(HashMap) 다루기 - 미생 - 티스토리, 8월 17, 2025에 액세스, https://hyunmin1906.tistory.com/316
  81. [Rust] 15. Hashmap (해쉬맵) - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/454
  82. [Rust] 컬렉션 - 해시맵 - 소소한 개발 공부, 8월 17, 2025에 액세스, https://soso-study.tistory.com/m/108
  83. 해쉬맵 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch08-03-hash-maps.html
  84. [Rust] 17. Panic! (복구 불가능한 에러 처리) - Thinking Different - 티스토리, 8월 17, 2025에 액세스, https://copynull.tistory.com/456
  85. Rust-004, 러스트 예외/에러 처리, 8월 17, 2025에 액세스, https://msjo.kr/2024/05/17/1/
  86. [Rust] Result와 Rust의 에러 처리 - 코딩 Rust - 티스토리, 8월 17, 2025에 액세스, https://baroqcat.tistory.com/entry/Rust-Result%EC%99%80-Rust%EC%9D%98-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC
  87. Result로 복구 가능한 에러 처리하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch09-02-recoverable-errors-with-result.html
  88. Rust의 에러 처리, Part 1, 8월 17, 2025에 액세스, https://blog.cro.sh/posts/error-handling-story-in-rust-part-1/
  89. Rust 제네릭 프로그래밍 기초 - LabEx, 8월 17, 2025에 액세스, https://labex.io/ko/tutorials/rust-exploring-rust-generics-functionality-99344
  90. [Rust] Generic - 코딩 Rust - 티스토리, 8월 17, 2025에 액세스, https://baroqcat.tistory.com/entry/Rust-Generic
  91. [Rust/러스트] 제네릭(Generic) 정리 - 미생 - 티스토리, 8월 17, 2025에 액세스, https://hyunmin1906.tistory.com/317
  92. 제네릭 데이터 타입 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch10-01-syntax.html
  93. 제네릭 - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/21-%EC%A0%9C%EB%84%A4%EB%A6%AD
  94. [RUST] 트레이트 (Traits) - 신승환의 기술 블로그, 8월 17, 2025에 액세스, https://goodbyeanma.tistory.com/186
  95. 트레잇: 공유 동작을 정의하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch10-02-traits.html
  96. Trait - 예제로 배우는 Rust 프로그래밍, 8월 17, 2025에 액세스, http://rust-lang.xyz/rust/article/22-Trait
  97. 트레이트로 공통된 동작을 정의하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch10-02-traits.html
  98. Rust 프로그래밍의 라이프타임 개념 - LabEx, 8월 17, 2025에 액세스, https://labex.io/ko/tutorials/rust-lifetime-concepts-in-rust-programming-99202
  99. [Rust] 함수 시그니처 lifetime에 대한 고찰 - int main(){ - 티스토리, 8월 17, 2025에 액세스, https://zzaekkii.tistory.com/28
  100. 라이프타임 - 박철우의 블로그, 8월 17, 2025에 액세스, https://parkcheolu.tistory.com/105
  101. [Rust] 라이프타임(lifetime) - 극꼼이 이야기 (GG_Tales) - 티스토리, 8월 17, 2025에 액세스, https://geukggom.tistory.com/315
  102. [Rust] 스마트 포인터 - YONGJIN LAB, 8월 17, 2025에 액세스, https://yongj.in/rust/rust-smart-pointers/
  103. RefCell
  104. RefCell
  105. 러스트로 구현하는 동시성: std::thread에서 Tokio까지 - Samsung Tech Blog, 8월 17, 2025에 액세스, https://techblog.samsung.com/blog/article/42
  106. 메세지 패싱 - The Rust Programming Language, 8월 17, 2025에 액세스, https://rinthel.github.io/rust-lang-book-ko/ch16-02-message-passing.html
  107. 메시지 패싱을 사용하여 스레드 간 데이터 전송하기 - The Rust Programming Language, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch16-02-message-passing.html
  108. 공유 상태 동시성, 8월 17, 2025에 액세스, https://doc.rust-kr.org/ch16-03-shared-state.html
  109. #28 공유 상태와 동시성 트레이트 - velog, 8월 17, 2025에 액세스, https://velog.io/@peeeeeter_j/Rust-Programming-28
  110. [Rust] 25. 스레드 동기화, 8월 17, 2025에 액세스, https://copynull.tistory.com/464
  111. [Rust] 외부 크레이트(Crate) 사용하기 - 데이로깅 - 티스토리, 8월 17, 2025에 액세스, https://luckylucky.tistory.com/13
  112. Rust 개발 필수 유틸: cargo edit - 인하대학교 인트아이, 8월 17, 2025에 액세스, https://int-i.github.io/rust/2021-09-19/rust-cargo-edit/