0%

슬라이스 타입이란

러스트 슬라이스 타입에 대해 공부한 내용을 정리합니다.


이전 글에서는 러스트만의 고유한 참조 방식에 대해서 살펴보았다. 러스트의 참조 방식은 데이터 경합을 유발할 수 있는 경우를 제외한 제한된 허용 하에서 사용할 수 있었다. 이번 글에서는 이런 러스트의 특성을 잘 나타내는 슬라이스 타입에 대해 정리할 것이다.

첫 번째 문자를 도출하기

어떤 문장에서 첫 번째 문자를 도출하고 싶다. 이런 경우 러스트에서 어떻게 코드를 만들 수 있을까? 아래의 코드를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main(){
let mut sen = String::from("Hello world!");
let num = first_word(&sen);
sen.clear();
println!("{}",num);
}
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()
}

위 코드는 “Hello world!”에서 첫 번째 단어의 글자 수를 반환해주는 함수이다. first_word()를 자세히 살펴보자.

먼저 first_word()는 문자열을 받아 이를 byte로 변환한다. 이렇게 변환하면 변수 bytes는 [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] 배열로 저장된다. 이를 for문으로 통해 인덱스와 함께 하나씩 살펴보면서 공백(32)과 동일한 수가 있다면 인덱스인 i를 반환한다.

위 함수는 그럴 듯 해보인다. 하지만 문자열 변수 sen가 변경되거나 사라져도 num값은 계속 유지된다. 위 코드에서 sen.clear()를 통해 변수 sen을 제거하였음에도 num은 사라지지 않는다. 이런 변수들의 독립성은 추후 컴파일단에서 잡히지 않는 버그가 될 수 있다. 나중에 변수 sen이 변경된 상태에서 num값을 사용하여 첫 번째 단어를 추출해도 프로그램 상 어떤 오류도 발생하지 않기 때문이다.

따라서 변수 sen이 변경되거나 사라지면 에러가 도출되는 함수를 만들고 싶다. 그럴 때 러스트의 슬라이스 타입을 사용할 수 있다.

슬라이스 타입이란

슬라이스(Slice) 타입은 소유권을 갖지 않으며, 문자열의 일부를 참조하는 방식이다. 위 예시에서 사용된 문자열 변수 sen을 통해 예시를 살펴보자.

1
2
3
4
5
6
7
fn main(){
let sen = String::from("Hello world!");
let first = &sen[..5];
let second = &sen[5..];
println!("{}", first);
println!("{}", second);
}

위 코드는 변수 sen의 값인 “Hello world!”에서 변수 first가 인덱스 0부터 5까지의 문자열을 참조로 가져오며, 변수 second는 인덱스 5에서부터 끝까지의 문자열을 참조로 가져온다. 한번 더 강조하지만 슬라이스 타입은 참조이기 때문에 변수 sen의 값이 변경된다면 first, second를 도출하는 데 문제가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn main(){
let mut sen = String::from("Hello world!");
let mut first = &sen[..5];
let mut second = &sen[5..];
sen.clear();
println!("{}", first);
println!("{}", second);
}
error[E0502]: cannot borrow `sen` as mutable because it is also borrowed as immutable
--> test.rs:5:5
|
3 | let mut first = &sen[..5];
| --- immutable borrow occurs here
4 | let mut second = &sen[5..];
5 | sen.clear();
| ^^^^^^^^^^^ mutable borrow occurs here
6 | println!("{}", first);
| ----- immutable borrow later used here

error: aborting due to previous error; 2 warnings emitted

For more information about this error, try `rustc --explain E0502`.

위 코드는 전의 코드와 다르게 에러를 반환한다. 차이는 sen.clear(); 이것 딱 하나이다. 에러를 자세히 살펴보면 슬라이스 타입에 이미 &로 참조가 되었기 때문에 변수 sen을 변경할 수 없다고 설명한다. 이처럼 슬라이스 타입을 사용한다면 앞서 봤던 오류를 해결할 수 있다.

슬라이스를 사용하여 첫 번째 문자를 도출하기

이제 배운 슬라이스 타입을 사용하여 첫 번째 문자를 도출해보자. 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main(){
let sen = String::from("Hello world!");
let num = first_word(&sen);
println!("{}",num);
}
fn first_word(s: &String) -> &str{
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate(){
if item == b' '{
return &s[..i];
}
}
&s[..]
}

위 코드는 전반적인 논리는 앞에서 설명한 바와 동일하다. 다만 발견한 첫 번째 문자의 마지막 인덱스를 사용하여 슬라이스 타입으로 첫 번째 단어를 도출한다. 이렇게 코드를 변경하면 변수 sen가 변경되었을 시에도 이전 문장의 첫 번째 단어를 가져올 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main(){
let sen = String::from("Hello world!");
let num = first_word(&sen);
let sen = String::from("changed");
println!("{}",num);
println!("{}",sen);
}
fn first_word(s: &String) -> &str{
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate(){
if item == b' '{
return &s[..i];
}
}
&s[..]
}

위 코드의 결과는 다음과 같다.

1
2
Hello
changed