Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

배열(Arrays)

“티켓 관리“에 대해 논의하려면 먼저 여러 개의 티켓을 한꺼번에 저장하는 방법부터 고민해야 합니다. 즉, 컬렉션(Collections), 그중에서도 특히 같은 타입의 여러 인스턴스를 저장하는 **동종 컬렉션(Homogeneous collection)**이 필요합니다.

이와 관련하여 Rust에서는 어떤 기능을 제공할까요?

배열(Arrays)

가장 먼저 떠올릴 수 있는 방법은 바로 **배열(Arrays)**입니다. Rust의 배열은 동일한 타입의 요소들로 구성된 고정 크기(fixed-size) 컬렉션입니다.

배열은 다음과 같이 정의합니다:

// 배열 타입 구문: [ <타입> ; <요소 수> ]
let numbers: [u32; 3] = [1, 2, 3];

이 코드는 1, 2, 3이라는 값으로 초기화된 3개의 정수 배열을 만듭니다. 배열의 타입은 [u32; 3]이며, “길이가 3인 u32 배열“이라고 읽습니다.

만약 모든 배열 요소를 동일한 값으로 채우고 싶다면, 조금 더 짧은 구문을 사용할 수 있습니다:

// [ <초깃값> ; <요소 수> ]
let numbers: [u32; 3] = [1; 3];

[1; 3]은 모두 1이라는 값을 가진 세 개의 요소로 구성된 배열을 생성합니다.

요소 접근

대괄호([])를 사용하여 배열의 개별 요소에 접근할 수 있습니다:

let first = numbers[0];
let second = numbers[1];
let third = numbers[2];

인덱스는 반드시 usize 타입이어야 합니다. 또한 Rust의 배열은 다른 언어와 마찬가지로 0부터 시작합니다. 문자열 슬라이스나 튜플에서 인덱스를 사용했던 방식과 같으니 익숙하실 것입니다.

범위 초과 접근

배열의 범위를 벗어난 인덱스로 요소에 접근하려고 하면 Rust는 **패닉(panic)**을 일으킵니다:

let numbers: [u32; 3] = [1, 2, 3];
let fourth = numbers[3]; // 이 코드는 런타임에 패닉을 일으킵니다!

Rust는 런타임에 **경계 검사(Bounds checking)**를 수행하여 이를 감시합니다. 약간의 성능 오버헤드가 발생하지만, 이를 통해 버퍼 오버플로우와 같은 치명적인 메모리 오류를 방지할 수 있습니다. 일부 상황에서는 Rust 컴파일러가 이 경계 검사를 최적화하여 없애기도 하는데, 나중에 반복자(Iterators)를 다룰 때 더 자세히 알아보겠습니다.

패닉을 일으키지 않고 안전하게 값을 가져오고 싶다면, Option<&T>를 반환하는 get 메서드를 사용하면 됩니다:

let numbers: [u32; 3] = [1, 2, 3];
assert_eq!(numbers.get(0), Some(&1));

// 범위를 벗어난 인덱스에 접근해도 패닉 대신 `None`을 얻습니다.
assert_eq!(numbers.get(3), None);

성능

배열의 크기는 컴파일 시점에 이미 결정되므로, 컴파일러는 배열을 스택(Stack) 메모리에 할당할 수 있습니다. 다음 코드를 실행할 때의 메모리 구조는 아래와 같습니다:

let numbers: [u32; 3] = [1, 2, 3];
        +---+---+---+
스택:  | 1 | 2 | 3 |
        +---+---+---+

배열이 차지하는 크기는 std::mem::size_of::<T>() * N입니다. (여기서 T는 요소의 타입, N은 요소의 개수입니다.) 덕분에 각 요소에 접근하거나 값을 바꾸는 작업은 매우 빠르게(O(1) 시간 복잡도) 이루어집니다.

Exercise

The exercise for this section is located in 06_ticket_management/01_arrays