배열과 슬라이스
배열은 연속된 메모리에 저장된 동일한 타입 T를 가진 객체들의 모음입니다. 배열은 대괄호 []를 사용하여 생성되며, 컴파일 타임에 알려진 배열의 길이는 타입 시그니처 [T; length]의 일부가 됩니다.
슬라이스는 배열과 유사하지만, 컴파일 타임에 그 길이를 알 수 없습니다. 대신, 슬라이스는 두 개의 워드(word)로 구성된 객체입니다. 첫 번째 워드는 데이터에 대한 포인터이고, 두 번째 워드는 슬라이스의 길이입니다. 워드 크기는 프로세서 아키텍처에 의해 결정되는 usize와 같으며, 예를 들어 x86-64에서는 64비트입니다. 슬라이스는 배열의 일부분을 빌려오는 데 사용될 수 있으며 &[T]라는 타입 시그니처를 가집니다.
use std::mem;
// 이 함수는 슬라이스를 빌려옵니다.
fn analyze_slice(slice: &[i32]) {
println!("슬라이스의 첫 번째 요소: {}", slice[0]);
println!("슬라이스는 {}개의 요소를 가지고 있습니다", slice.len());
}
fn main() {
// 고정 크기 배열 (타입 시그니처는 생략 가능합니다).
let xs: [i32; 5] = [1, 2, 3, 4, 5];
// 모든 요소를 동일한 값으로 초기화할 수 있습니다.
let ys: [i32; 500] = [0; 500];
// 인덱싱은 0부터 시작합니다.
println!("배열의 첫 번째 요소: {}", xs[0]);
println!("배열의 두 번째 요소: {}", xs[1]);
// `len`은 배열의 요소 개수를 반환합니다.
println!("배열의 요소 개수: {}", xs.len());
// 배열은 스택에 할당됩니다.
println!("배열의 점유 메모리: {} 바이트", mem::size_of_val(&xs));
// 배열은 자동으로 슬라이스로 빌려올 수 있습니다.
println!("배열 전체를 슬라이스로 빌려옵니다.");
analyze_slice(&xs);
// 슬라이스는 배열의 한 섹션을 가리킬 수 있습니다.
// [시작_인덱스..종료_인덱스] 형태입니다.
// `시작_인덱스`는 슬라이스의 첫 번째 위치입니다.
// `종료_인덱스`는 슬라이스의 마지막 위치보다 하나 더 큰 값입니다.
println!("배열의 한 섹션을 슬라이스로 빌려옵니다.");
analyze_slice(&ys[1 .. 4]);
// 빈 슬라이스 `&[]`의 예시:
let empty_array: [u32; 0] = [];
assert_eq!(&empty_array, &[]);
assert_eq!(&empty_array, &[][..]); // 동일하지만 더 장황한 표현
// 배열은 `.get`을 사용하여 안전하게 접근할 수 있으며, 이는 `Option`을 반환합니다.
// 이는 아래와 같이 매치(match)될 수 있으며, 프로그램을 계속 진행하는 대신
// 친절한 메시지와 함께 종료하고 싶다면 `.expect()`와 함께 사용될 수 있습니다.
for i in 0..xs.len() + 1 { // 이런, 범위를 하나 벗어났네요!
match xs.get(i) {
Some(xval) => println!("{}: {}", i, xval),
None => println!("진정하세요! {}는 너무 멀리 갔습니다!", i),
}
}
// 상수 값을 사용한 배열의 범위를 벗어난 인덱싱은 컴파일 타임 에러를 발생시킵니다.
//println!("{}", xs[5]);
// 슬라이스에서 범위를 벗어난 인덱싱은 런타임 에러를 발생시킵니다.
//println!("{}", xs[..][5]);
}