Rust 슬라이스
슬라이스(slice)를 쓰면 배열이나 벡터의 일부분을 대여할 수 있어요. 데이터의 한 구간을 들여다보는 “창문” 같은 것이라서, 데이터를 복사하지도 않고 소유권을 가져가지도 않아요. 값 하나를 대여해 본 적이 있다면, 슬라이스는 같은 아이디어를 여러 요소의 범위에 적용한 거라고 생각하면 돼요.
배열 자르기
섹션 제목: “배열 자르기”fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // 인덱스 1, 2, 3의 요소 println!("{:?}", middle);}실행 결과:
[20, 30, 40]1..4는 for 반복문에서 봤던 것과 같은 범위예요. 인덱스 1에서 시작해서 인덱스 4 직전에 멈추기 때문에, 인덱스 1, 2, 3을 얻게 돼요.
범위를 줄여 쓰기
섹션 제목: “범위를 줄여 쓰기”자주 쓰이는 줄임 표현이 몇 가지 있어요.
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // 처음부터 인덱스 3 직전까지 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // 인덱스 2부터 끝까지 -> [30, 40, 50] println!("{:?}", &numbers[..]); // 전체 -> [10, 20, 30, 40, 50]}문자열 슬라이스
섹션 제목: “문자열 슬라이스”사실 여러분은 이미 슬라이스를 쓰고 있었어요. &str은 String(또는 다른 문자열)의 슬라이스거든요! 그래서 이 두 타입이 서로 잘 어울리는 거예요.
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}여기서는 문자열을 영어 그대로 두었어요. [0..3]은 글자 수가 아니라 바이트 위치로 자르는데, 한글 한 글자는 UTF-8에서 3바이트를 차지해서 결과가 달라지기 때문이에요.
왜 복사하지 않고 슬라이스를 쓸까요?
섹션 제목: “왜 복사하지 않고 슬라이스를 쓸까요?”원하는 구간만큼 새 Vec이나 String을 복사해서 만드는 방법도 떠올릴 수 있어요. 슬라이스는 그럴 필요가 아예 없어요. 참조자(&)가 값 하나를 가리키듯이, 슬라이스는 기존 데이터의 일부를 가리키기만 하거든요. 그러니 추가 메모리 할당도 없고 복사도 없어요. 그리고 대여 페이지에서 배운 대여 규칙도 그대로 적용돼요.
슬라이스를 받는 함수
섹션 제목: “슬라이스를 받는 함수”슬라이스는 함수 매개변수로 특히 유용해요. 배열과 벡터를 모두 받을 수 있으니까요.
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32]는 배열의 슬라이스든 Vec의 슬라이스든 둘 다 받아요. 함수 하나로 두 가지 호출이 모두 동작해요.
가변 슬라이스로 데이터 바꾸기
섹션 제목: “가변 슬라이스로 데이터 바꾸기”지금까지의 슬라이스는 데이터를 읽기만 했어요. 가변 슬라이스 &mut [T]를 쓰면 함수가 전달받은 요소를 바꿀 수 있어요. 단, 전달받은 요소만요.
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // 인덱스 1과 2만 println!("{:?}", scores);
double_all(&mut scores); // 벡터 전체 println!("{:?}", scores);}실행 결과:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]첫 번째 호출은 &mut scores[1..3]을 넘기기 때문에, double_all은 인덱스 1과 2만 볼 수 있어요. 그 범위 밖의 요소는 건드릴 수 없으니 안전해요. 함수 안에서는 슬라이스가 다시 인덱스 0부터 시작해요. double_all 입장에서는 그냥 숫자 두 개짜리 목록일 뿐이에요.
이게 안전한 이유는 대여 페이지에서 제대로 배울 규칙 덕분이에요. 함수가 &mut 슬라이스를 들고 있는 동안에는 다른 어떤 코드도 그 요소들을 쓸 수 없어서, 코드의 두 부분이 동시에 같은 요소를 바꾸는 일이 절대 생기지 않아요. 그리고 벡터도 mut로 선언해야 해요. 그렇지 않으면 Rust가 가변으로 대여하는 걸 허락하지 않아요.
연습 문제
섹션 제목: “연습 문제”1. 반으로 나누기
섹션 제목: “1. 반으로 나누기”벡터 vec![3, 6, 9, 12, 15, 18]을 만들고, 앞쪽 절반과 뒤쪽 절반을 각각 슬라이스로 출력해 보세요. 힌트: let mid = numbers.len() / 2;를 계산한 다음 ..mid와 mid..를 쓰세요.
정답 보기
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("앞쪽 절반: {:?}", first_half); println!("뒤쪽 절반: {:?}", second_half);}실행 결과:
앞쪽 절반: [3, 6, 9]뒤쪽 절반: [12, 15, 18]mid를 len()으로 계산하기 때문에, 숫자를 더하거나 빼도 계속 잘 동작해요. 길이가 홀수이면 남는 하나는 뒤쪽 절반에 들어가요.
2. 어떤 목록에서든 가장 큰 수 찾기
섹션 제목: “2. 어떤 목록에서든 가장 큰 수 찾기”가장 큰 수를 돌려주는 함수 largest(numbers: &[i32]) -> i32를 작성해 보세요. 배열로 한 번, 벡터로 한 번, 그리고 벡터의 첫 번째 요소만 담은 슬라이스로 한 번 호출해 보세요. 힌트: numbers[0]에서 시작해서 나머지를 반복문으로 훑어보세요. 배열에서 하던 것처럼요.
정답 보기
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}실행 결과:
93510슬라이스를 반복하면 숫자 자체가 아니라 각 요소의 참조자(&i32)를 받게 돼요. *n은 “n이 가리키는 값”이라는 뜻이라서, *n > biggest는 평범한 숫자 두 개를 비교해요. 이 코드는 빈 슬라이스를 받으면 패닉이 일어나요. numbers[0]이 존재하지 않으니까요. 이런 경우를 위해 Option을 돌려주는 방법을 나중에 배울 거예요.
- 슬라이스(
&[T])는 배열이나 벡터의 일부를 복사하지 않고 대여해요. 1..4는 인덱스 1부터 3까지를 골라요...의 한쪽을 비워 두면 처음부터, 또는 끝까지를 뜻해요.&str자체가 텍스트의 슬라이스예요. 그래서 문자열과 슬라이스는 동작이 많이 닮았어요.- 배열과 벡터를 모두 받고 싶다면 함수 매개변수 타입으로
&[T]를 쓰는 게 좋아요. - 가변 슬라이스(
&mut [T])를 쓰면 함수가 전달받은 요소만 바꿀 수 있어요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 슬라이스를 쓰면 배열이나 벡터의 일부로 무엇을 할 수 있나요?
2. &numbers[1..4]에는 어떤 인덱스가 포함되나요?
3. 함수 매개변수 타입으로 &[i32]가 좋은 이유는 무엇인가요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- 슬라이스 — The Rust Programming Language 한국어판: “문자열의 첫 번째 단어” 예제로 슬라이스를 차근차근 설명해요.
- Arrays and Slices — Rust by Example (영어): 배열을 슬라이스로 대여하는 예제를 직접 실행해 볼 수 있어요.
slice— 표준 라이브러리 문서 (영어):first,split_at,contains,sort처럼 슬라이스가 가진 다양한 메서드를 볼 수 있어요.- Slice types — The Rust Reference (영어):
[T]와&[T]가 정확히 무엇을 뜻하는지 알고 싶다면 이 정의를 보세요.