슬라이스(Slices)
Vec의 메모리 레이아웃을 다시 한 번 떠올려 볼까요?
let mut numbers = Vec::with_capacity(3);
numbers.push(1);
numbers.push(2);
+---------+--------+----------+
스택 | 포인터 | 길이 | 용량 |
| | | 2 | 3 |
+--|------+--------+----------+
|
|
v
+---+---+---+
힙: | 1 | 2 | ? |
+---+---+---+
이미 앞에서 String이 실제로는 Vec<u8>을 감싼 형태라는 것을 언급했었죠. 그렇다면 자연스럽게 이런 궁금증이 생길 수 있습니다. “Vec에 대응하는 &str 같은 타입은 없을까?”
&[T]
[T]는 타입 T의 연속된 요소 시퀀스를 나타내는 **슬라이스(Slice)**입니다. 주로 빌려온 형태인 &[T]를 가장 많이 사용하게 됩니다.
Vec에서 슬라이스 참조를 생성하는 방법은 여러 가지가 있습니다.
let numbers = vec![1, 2, 3];
// 인덱싱 구문을 사용하여 전체 슬라이스 참조 let slice: &[i32] = &numbers[..];
// 전용 메소드를 사용하여 슬라이스 참조 let slice: &[i32] = numbers.as_slice();
// 요소 중 일부만을 포함하는 슬라이스 참조 let slice: &[i32] = &numbers[1..];
Vec은 [T] 타입을 대상으로 Deref 트레이트를 구현하고 있습니다. 따라서 역참조 강제 변환(Deref coercion) 덕분에 Vec에서 슬라이스 전용 메소드를 직접 호출할 수 있습니다.
let numbers = vec![1, 2, 3];
// 놀랍게도, `iter`는 `Vec`의 메소드가 아닙니다!
// `&[T]`의 메소드이지만 역참조 강제 변환 덕분에 `Vec`에서도 바로 사용할 수 있죠.
let sum: i32 = numbers.iter().sum();
메모리 레이아웃(Memory Layout)
&[T]는 &str과 마찬가지로 **팻 포인터(fat pointer)**입니다. 슬라이스의 첫 번째 요소를 가리키는 포인터와 슬라이스의 길이라는 두 가지 정보로 구성됩니다.
요소가 세 개인 Vec이 있다고 가정해 봅시다.
let numbers = vec![1, 2, 3];
이 Vec의 일부분에 대한 슬라이스 참조를 만들면 다음과 같습니다.
let slice: &[i32] = &numbers[1..];
메모리 레이아웃은 다음과 같은 구조를 가집니다.
numbers slice
+---------+--------+----------+ +---------+--------+
스택 | 포인터 | 길이 | 용량 | | 포인터 | 길이 |
| | | 3 | 4 | | | | 2 |
+----|----+--------+----------+ +----|----+--------+
| |
| |
v |
+---+---+---+---+ |
힙: | 1 | 2 | 3 | ? | |
+---+---+---+---+ |
^ |
| |
+--------------------------------+
&Vec<T> 대 &[T]
함수 인자로 Vec에 대한 불변 참조를 넘겨야 할 때, 가급적 &Vec<T>보다는 &[T]를 사용하는 것이 좋습니다.
&[T]를 사용하면 Vec뿐만 아니라 모든 종류의 슬라이스를 인자로 받을 수 있어 함수의 범용성이 높아지기 때문입니다.
예를 들어, Vec의 일부 요소를 전달하거나 **배열(array)**의 일부를 슬라이스로 전달할 수도 있습니다.
let array = [1, 2, 3];
let slice: &[i32] = &array;
배열의 슬라이스와 Vec의 슬라이스는 동일한 타입입니다. 둘 다 메모리상에 연속적으로 나열된 요소를 가리키는 팻 포인터일 뿐입니다. 배열의 경우 포인터가 힙(heap)이 아닌 스택(stack) 영역을 가리키게 되지만, 슬라이스를 사용하는 방식에는 아무런 차이가 없습니다.
Exercise
The exercise for this section is located in 06_ticket_management/10_slices