[Rust] Dynamically Sized Type(DST) & str

suwonyoo·2025년 2월 3일

Rust Study | Special

목록 보기
3/4
post-thumbnail

간단하면서도 재미있는 주제이다. 우리는 왜 str을 그대로 쓰지 못하고, &str형식으로 써야 하며, String은 단독으로 사용할 수 있을까? 이를 완벽하게 이해하고 설명할 수 있기 위해, 밑바닥부터 차례대로 정리해보는 시간을 가져보겠다.


1. Rust의 특징

  • 러스트는 컴파일 타임에서 철저한 검사를 수행하여 런타임 오류를 줄이는 것이 특징이다.
  • 러스트는 C/C++과 같이 AOT(Ahead-Of-Time)으로, 소스 코드를 미리 기계어로 컴파일하는 방식을 이용한다. 이는 JIT (Java, Python, Js)와 같이 실행 중간에 컴파일하는 언어와 다르게, 실행 성능이 빠르고 런타임 비용이 없다는 장점이 있다.
    - JIT는 컴파일타임과 런타임이 섞여있는 방식이다.

2. 컴파일 타임(Compile time)

  • 프로그램이 실행되기 전에, 컴파일러가 소스 코드를 분석하고 최적화하며 기계어(Binary 명령어)로 변환

    Source code ->LLVM IR -> Assembly -> Machine Code(CPU가 이해할 수 있는 언어)

  • 컴파일 시 변수의 타입이 결정되는 정적 타이핑(Static Typing)을 사용하므로, 컴파일 타임에 모든 타입 오류를 검출

  • Syntax Analysis, Type Checking, Ownership & Borrowing Check, Lifetime Analysis, Optimization

  • 이 때, const, static, 문자열 리터럴(&str)과 같은 정적 데이터는 컴파일 타임에 메모리가 할당

  • 타입저장 위치런타임 변경 가능 여부특이점
    const🔹 Read-Only 데이터 영역 (임베디드 가능)❌ 변경 불가능값이 실행 파일에 직접 포함됨 (컴파일 시 결정됨)
    static🔸 Read-Only or Static Data (RW 가능)mut 가능static mut이면 런타임 변경 가능 (데이터 영역에 위치)
    &str (문자열 리터럴)🔹 Read-Only 데이터 영역❌ 변경 불가능실행 파일 내에 저장됨

3. 런타임(Runtime)

  • 프로그램이 실제로 실행되는 상태
  • 실행 중 메모리 할당 및 해제, 연산 수행, 파일 읽기/쓰기, 네트워크 통신 등이 이루어짐.

    이 때, 메모리 할당 및 해제는 스택(Stack)과 힙(Heap)을 활용합니다.

  • GC(Garbace Collector)가 없으므로 런타임 오버헤드가 거의 없음.

4. Rust 실행 과정

  • 단계역할Rust에서의 예제
    소스 코드 작성Rust 코드 작성fn main() { let x = 5; println!("{}", x); }
    컴파일 (AOT)소스 코드 → 기계어 변환rustc main.rs
    LLVM IR 변환중간 표현(LLVM IR)로 변환alloca i32, store, load, add
    어셈블리 변환CPU 명령어로 변환mov eax, 5, add eax, 10
    기계어 변환CPU가 실행할 바이너리 생성10111000 00000101 ...
    OS가 실행 파일 로드실행 파일을 메모리에 적재./main
    CPU가 명령어 실행기계어를 실행하여 결과 출력mov, add, call println
    시스템 콜OS에 출력 요청syscall (write stdout)
    프로그램 종료종료 및 메모리 해제Rust의 자동 메모리 해제

5. 스택(Stack)과 힙(Heap)

스택(Stack)

  • 후입선출(LIFO, Last In First Out) 구조
  • 크기가 고정된 값을 저장 (예: i32, bool)
  • 데이터 접근이 빠름
  • 푸시(push)팝(pop) 작업으로 관리
  • 스택에 있는 데이터들은 Copy

힙(Heap)

  • 크기가 변동 가능한 데이터를 저장 (예: String, Vec<T>)
  • 포인터(pointer) 를 사용하여 메모리 접근
  • 데이터를 저장할 때 메모리 할당 요청이 필요함
  • 접근 속도가 상대적으로 느림

6. 그렇다면.. 왜 str을 단독으로 사용할 수 없을까?

a. str은 DST(Dynamic Sized Type)이다. 크기가 동적으로 변한다.

  • 본질적으로 DST으로, 해당 타입의 size을 컴파일 타임에서 알 수 없다.
  • 이는 먼저 크기가 고정된 값을 저장하는 스택에는 직접 저장할 수 없는 이유이다.

질문) 아니

let s : str = "hello"; 

하면 되는거 아닌가요?, 해당 문자열 리터럴 hello 이미 크기 결정되어 있는데....? 아니 str의 설계 자체가 원래 컴파일 타임에서 알 수 없는 것. 해당 str의 원래 크기가 동적으로 변할 수 있는 타입으로 설계되었기 때문에

b. 이러면 결국.. 남은건 Heap

  • 크기가 변동 가능한 데이터를 저장하는 Heap에 런타임 때 메모리를 할당하여 str을 저장한다.

c. 그런데, Heap의 데이터를 직접 다루지 못한다(메모리 안정성을 위해서)

  • str은 Heap에 저장되어 있는데... 근데 해당 str을 직접 다뤄야 할 일이 무조건 생길텐데..

d. 아, 포인터를 이용하면 되겠구나, 포인터 자체의 크기는 정해져 있으니깐 이를 stack에 저장 가능하겠네.

  • String을 예시로 들어보자.
    내부적으로 Stringstr을 힙에 저장하고, 이를 가리키는 포인터를 스택에 유지한다.
fn main() {
    let s: String = String::from("hello");
}
스택(Stack)(Heap)
-------------------------------------------------
s: String {
    ptr: ────────────▶ "hello" (5바이트)  
    length: 5
    capacity: 5 (or more)
}
-------------------------------------------------
  • 이러면 결국 해당 구조체는 런타임때 크기를 알 수 있으므로, 이를 스택에 저장하면 말끔하게 해결, 스택의 구조체를 이용하여 Heap의 str데이터를 접근하면 문제 해결.

  • 런타임때 Heap영역의 str의 크기가 변하더라도, stack안의 String자체의 크기는 항상 일정함. pointer를 썻기 때문.

pub struct Vec<T> {
    ptr: *mut T,   // ✅ 힙 데이터를 가리키는 포인터
    length: usize, // ✅ 현재 저장된 데이터 개수
    capacity: usize, // ✅ 할당된 총 메모리 크기
}

결론적으로 str은 설계 자체부터 DST이므로, 크기가 정해져 있지 않아 Heap에 할당을 해야 하며, 이를 사용하기 위해서는 stack에서 포인터 기반 구조체를 통해 가능한 케이스구나.

e. 데이터 영역(Read-Only, Static Memory)에 저장된 경우도?

fn main() {
    let s: &str = "hello"; // ✅ `&str`을 통해 문자열 리터럴을 참조
}
Stack                           Data (Read-Only)
-------------------------------------------------
s: &str  ────────────▶ "hello" (5바이트, Read-Only)
-------------------------------------------------
  • 이는 런타임이 아닌 컴파일 타임때 메모리 할당이 되지만, 마찬가지로 str이 아닌 &str 자체는 크기가 고정되어 있으므로 이는 stack에 할당이 된다.

정리

  1. String자체는 컴파일 타임에 고정된 크기를 지닌 Sized이다. 그렇기 때문에 해당 포인터 기반 구조체가 Stack에 런타임때 메모리 할당 후 저장이 된다. 하지만 String 내부적으로 Heap에 str를 저장하기 때문에, strDST이므로 런타임에 동적으로 변할 수 있다.
  2. strunsized 타입(or DST 타입)으로, 컴파일 타임 and 런타임에 크기를 가늠할 수 없는 바이트 스트림이다.

0개의 댓글