슬라이스는 컬렉션을 통째로 참조하는 것이 아닌 연속된 일련의 요소를 참조하도록 해줍니다.
슬라이스는 참조자의 일종으로 소유권을 갖지 않습니다.
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()
}
위 예제는 함수의 인자로 받은 문자열에서 첫번째 공백문자를 찾아 리턴하는 예제 입니다.
우선 슬라이스 개념 없이 이를 수행해보도록 하겠습니다.
let bytes = s.as_bytes();
// 공백 여부 체크를 위해 바이트로 변환
Rust의 문자열은 UTF-8로 인코딩 되어있습니다. 그렇기 때문에 문자열을 비교하려면 바이트 단위로 접근해야합니다.
따라서 문자열을 바이트 배열로 변환합니다.
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
이후 iterator를 이용해 해당 배열을 순회하면서 공백을 찾습니다.
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // 변수를 비워서 ""로 만든다
}
자 이함수를 실제로 사용해보면 한가지 문제가 있습니다.
s.clear()부분을 보면 s변수의 값을 ""로 만들어버립니다.
이런경우 word에 있는 값은 아무런 의미가 없어집니다.
즉 5라는 값을 가지고는 있지만 s와 논리적으로 관련이 없어집니다.
컴파일러 입장에서도 이는 당연히 문제있는 코드가 아니니 에러를 발생시키지 않습니다.
결국 이런 방식은 논리적 버그를 초래할 가능성을 내포하고 있습니다.
Slice
아래와 같이 slice라는 개념을 활용할 수 있습니다.
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6...11];
문자열 슬라이스 (string slice)는 String의 일부를 가리키는 참조자를 말합니다.
해당 값들은 실제 문자열의 x번부터 x2번까지의 바이트를 참조합니다.
s의 실제 메모리 주소를 참조하고 이 주소와 범위 정보를 함께 저장합니다.
C계열에서 포인터라고 부르는 것과 유사한 형태로 보입니다.
다만 slice는 메모리 주소 뿐만 아니라 길이 정보를 모두 포함하고 있다는 점이 다릅니다.
Rust의 철학
자 두가지 방식 모두 살펴보니 왜 slice를 써야할지 감이 좀 옵니다.
Rust가 추구하는 철학에 맞게 코드를 작성해야 컴파일 타임에서 안전하게 버그를 발견해낼 수 있기 때문이다 라고 생각 해볼 수 있을 것 같습니다.
Slice의 사용
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for(i, &item) in bytes.iter().enumerate(){
if item == b'' {
// slice로 만들어 리턴
return &s[0..i];
}
}
&s[..]
}
빈 문자열을 찾으면 slice로 만들어서 리턴하는 실제 사용 예시 입니다.
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // 에러!
println!("the first word is: {}", word);
}
그런데 해당 함수를 호출하고 기존 문자열을 s.clear()를 통해 제거하는 부분에서 에러가 발생합니다.
first_word 함수에서 s에 대한 읽기전용 불변참조 ( 슬라이스 )를 반환하고 있기 때문에 word에는 불변 참조 값이 저장되어 있습니다.
Rust의 소유권 및 참조 규칙에 따르면 Rust는 불변 참조와 가변 참조를 동시에 허용하지 않습니다.
불변 참조는 읽기 전용이므로 해당 참조가 유효한 동안 원본데이터 (s)를 변경할 수 없는 상태입니다.
따라서 s.clear() 를 통해 가변 참조로 빌리는걸 컴파일러에서 미리 방지하고 체크해줍니다
왜냐하면 Rust 에서는 가변 참조가 사용 중인 동안, 불변참조가 함께존재하면 Data Race 위험이 있다고 판단하기 때문입니다.
DataRace란 멀티 쓰레드/프로세스 환경에서 일어나는 오류를 말합니다. 공유 데이터에 동시 접근 할 때 생기는 문제입니다.
슬라이스로써의 문자열 리터럴
문자열 리터럴은 프로그램의 바이너리 파일에 포함되어 있으며, 실행 시 읽기 전용 메모리 영역에 저장됩니다.
문자열 리터럴은 바이너리 내 특정 메모리 범위를 지정하고 있는 슬라이스(&str)로 볼 수 있습니다.
'Rust' 카테고리의 다른 글
Rust 기본개념 - 열거형 (0) | 2024.12.08 |
---|---|
Rust 기본개념 - 메서드 (0) | 2024.12.07 |
Rust 기본 개념 - 구조체 (2) | 2024.12.07 |
Rust 기본 개념 - 참조 및 빌림 (0) | 2024.05.15 |
Rust 기본 개념 - 소유권 (0) | 2024.05.15 |