4.3. 슬라이스
슬라이스
슬라이스는 컬렉션의 연속된 일련의 요소를 참조한다.
이것 역시 참조자이기에 소유권이 없다.
슬라이스 필요성
문자열의 첫번째 문자를 알고 싶을 때 다음과 같은 함수를 만들어 활용할 수 있다.
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
위 코드는 문자열을 받았을 때 첫번째 문자의 길이를 반환하는 함수이다.
먼저 문자열을 바이트 단위로 쪼개고, 이를 순회한다.
enumerate를 통해 인덱스도 함께 가져온다.
item에 대해서 &는 값을 참조한다는 의미임을 생각하자.
이 코드는 문제없이 실행되지만 아쉬운 점이 있다.
원본에 해당하는 s가 해제되었을 경우 이 함수의 반환값은 의미가 없어지는데, 해당 값은 그대로 유지된다는 점이다.
이 함수로 뽑아낸 인덱스를 통해 해제되어버린 외부 s에 접근한다고 생각해보자면 이는 런타임에 에러를 일으킬 것이다.
이러한 상황에서 사용할 수 있는 것이 바로 문자열 슬라이스이다.
문자열 슬라이스
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
위 코드가 슬라이스의 예시이다.
생김새는 참조자에 배열을 섞은 형태인데 이는 String의 일부분을 가리키는 참조자이다.
슬라이스는 내부적으로 시작위치와 길이를 데이터 구조에 저장한다.
이러한 방식으로 world 변수가 저장된 것이 확인된다.
.. 범위는 앞이 0으로 시작할 때 0을 생략할 수 있다.
또 마지막 값까지 간다면 맨 뒤를 생략할 수 있다.
그러니 [..]는 해당 문자열의 모든 값을 그대로 가져오는 슬라이스이다.
문자열이 utf-8일 경우에는 별도의 설정이 필요하다.
해당 내용에 대해서는 8.2. 문자열에 UTF-8 텍스트 저장하기 참조
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
이상의 내용으로 코드를 이렇게 바꿀 수 있다.
위 코드에서 s는 별다른 값이 아니라, 기존의 문자열의 인덱스들의 정보를 담는 참조자이다.
&str이 반환되는데, 이것이 바로 문자열 슬라이스의 타입이다.
이 코드의 장점은, 참조자가 사용되었기에 index out of error가 발생하지 않는다는 점이 보장된다는 것이다.
이제는 이 함수를 쓴 코드에서 인덱스를 넘기는 동작이 수행됐을 때 바로 에러가 발생하게 될 것이다.
위 코드에서 fw는 s와 관련된 불변 참조자이다.
그래서 clear라는 가변 연산이 일어난 이후 사용되게 된다면 컴파일 에러가 발생한다.
의도되지 않는 동작이기 때문이다.
불변 참조자가 있을 때는 가변 참조자를 만들 수 없는 규칙을 상기하라.
문자열 슬라이스를 매개변수로 사용하기
문자열 리터럴은 바이너리 내에 저장된다.
let s = "Hello wolrd";
이 코드에서 s는 사실 문자열 슬라이스이다.
즉, &str 타입이다.
참고로 &str은 String의 참조자를 받아낼 수 있다.
String의 참조자는 문자열 슬라이스가 된다는 뜻이기도 한데, 이는 15.2. Deref 트레이트로 스마트 포인터를 보통의 참조자처럼 취급하기에서 다시 보게 될 것이다.
이러한 점을 사용해, String의 참조자 타입을 명시하는 부분에서 슬라이스를 활용하면 유연한 코드 작성이 가능하다.
first_word 함수의 인자는 문자열 슬라이스이다.
그래서 4,5,10,11 줄은 기본적으로 문제 없이 실행 가능하다.
6번 줄은 String이 역참조로 바뀐 케이스이다.
12번 줄은 원래 문자열 리터럴은 문자열 슬라이스이기에 사용가능한 것이다.
그 외의 슬라이스
문자열 뿐 아니라 모든 컬렉션에 사용가능한 슬라이스 타입역시 존재한다.
기본 슬라이스는 &[i32]타입이다.
이렇게 소유권, 대여, 슬라이스를 통해 러스트는 컴파일 타임에 메모리 안정성을 보장한다.
사용법만 익힌다면, 이후 다른 개발자의 코드를 사용할 때도 데이터 정리가 자동으로 이뤄지니 메모리에 대한 걱정 없이 개발을 이어나갈 수 있다는 장점까지 있다.