# 슬라이스(slices)

슬라이스는 러스트에서 어떤 연속적인 데이터에 대한 부분 참조를 안전하게 제공하는 기능이다. 슬라이스는 원본 데이터를 복사하지 않고, 단지 그 일부만을 ‘참조’하는 역할을 하기 때문에 메모리 사용을 줄이고, 동시에 러스트의 소유권 규칙을 지키면서도 유연한 데이터 접근이 가능해진다. 슬라이스는 실제로 참조(reference)와 비슷한 방식으로 작동하지만, 시작 위치와 길이 정보를 별도로 저장함으로써 특정 구간만을 가리킬 수 있다. 러스트는 슬라이스가 유효 범위를 벗어나거나, 부정확한 인덱스로 접근하는 문제를 컴파일 및 실행 시점에 철저히 검사하여 안전성을 보장한다.

슬라이스는 주로 문자열에 대해서 많이 사용되지만, 어떤 연속된 요소를 가진 데이터 구조(예: 배열, 벡터)에도 적용할 수 있다. 슬라이스는 소유권을 갖지 않고, 해당 구간에 대한 불변 참조(\&T) 형태로 사용된다. 불변 참조뿐만 아니라 가변 참조(\&mut T)를 통해서 슬라이스를 생성하는 일도 가능하지만, 이는 동시에 다른 참조가 없어야 하는 러스트의 빌림(borrowing) 규칙을 따라야 한다. 슬라이스가 유효한 동안 원본 데이터는 제거되거나 변경되어서는 안 되며, 반대로 원본 데이터가 변경될 때에도 슬라이스가 허용되는지 여부는 빌림 규칙에 따라 결정된다.

슬라이스는 러스트에서 매우 중요한 개념인 동시에 다른 언어의 배열 혹은 문자열 처리 방식과 다른 면이 있어 주의 깊게 이해해야 한다. 예를 들어 C 계열 언어에서 문자열 포인터를 이용해 부분 문자열을 얻을 때는 사용자가 직접 문자열의 유효 범위를 관리해야 하고, 문자열 길이 초과 등 다양한 에러를 일일이 처리해야 한다. 하지만 러스트에서 슬라이스는 안전한 메모리 접근을 보장하고, 잘못된 인덱스로 접근할 경우 컴파일 에러나 런타임 에러로 이어지므로 개발자가 이러한 위험을 일일이 관리하지 않아도 된다.

슬라이스 구문은 \[시작..끝] 형태로 나타낸다. 시작 인덱스는 포함되지만 끝 인덱스는 포함되지 않는다. 시작 인덱스를 생략하면 0부터, 끝 인덱스를 생략하면 데이터의 전체 길이까지로 간주한다. 즉, \&s\[..] 형태는 전체 데이터에 대한 슬라이스가 된다.

아래 예시는 문자열 슬라이스를 간단하게 보여준다.

```rust
fn main() {
    let s = String::from("Hello, Rust!");
    let slice_all = &s[..];      // 전체 문자열을 슬라이스
    let slice_hello = &s[0..5];  // "Hello"
    let slice_rust = &s[7..11];  // "Rust"

    println!("{}", slice_all);
    println!("{}", slice_hello);
    println!("{}", slice_rust);
}
```

슬라이스는 실제로 \&str 타입으로 표현된다. 이 타입은 “문자열 슬라이스”를 의미하며, String 타입과 달리 스택에 참조 정보(시작 지점과 길이)만 저장하고, 힙에 있는 데이터 자체에 대한 소유권은 가지지 않는다. 이처럼 슬라이스는 원본 데이터를 소유하지 않고, 단지 빌려 쓰는 개념이므로 원본이 유효한 동안에만 사용할 수 있다.

슬라이스는 문자열에만 국한되지 않고, 배열이나 벡터에도 적용할 수 있다. 배열이나 벡터는 연속적인 요소를 가지므로 \[시작..끝] 표기법을 통해 그 구간을 참조하는 슬라이스를 만들 수 있다.

```rust
fn main() {
    let numbers = [10, 20, 30, 40, 50];
    let slice_part = &numbers[1..4]; // [20, 30, 40]

    for num in slice_part {
        println!("{}", num);
    }
}
```

이 예시에서 slice\_part는 불변 슬라이스(&\[i32])가 되며, numbers의 1번 인덱스부터 3번 인덱스까지(4번 인덱스는 포함하지 않음)를 안전하게 참조한다. 원본 배열 numbers가 스코프 내에 유효한 한 slice\_part도 유효하며, numbers가 유효 범위를 벗어나면 slice\_part 역시 사용할 수 없다.

가변 슬라이스(\&mut \[T])를 이용하면 슬라이스를 통해 부분 데이터를 수정할 수도 있다. 다만 러스트는 불변 참조와 가변 참조가 동시에 유효하지 않도록 하는 규칙을 적용하므로, 가변 슬라이스를 사용하려면 해당 배열 혹은 벡터에 대해 다른 불변 빌림이 존재하지 않아야 한다.

```rust
fn main() {
    let mut values = [1, 2, 3, 4, 5];
    let slice_mut = &mut values[2..4]; // [3, 4] 참조
    slice_mut[0] = 10;
    slice_mut[1] = 20;

    for val in &values {
        println!("{}", val); // 1, 2, 10, 20, 5
    }
}
```

이 코드에서는 slice\_mut이 values의 일부에 대해 가변 슬라이스를 만들어, 그 구간의 요소들을 변경한다. 슬라이스는 연속된 구간에만 적용할 수 있고, 중간중간 인덱스를 건너뛰는 식으로는 지정할 수 없다.

슬라이스는 내부적으로 시작 지점과 길이 정보를 갖기 때문에, 슬라이스의 범위를 벗어난 부분에 접근하려고 하면 컴파일 에러나 런타임 에러가 발생한다. 예를 들어 \&s\[0..100]처럼 원본 문자열보다 큰 인덱스를 슬라이스 하려고 시도하면 프로그램은 정상적으로 컴파일되지 않거나, 실행 시 에러가 발생하여 안전성이 보장된다.

슬라이스가 유효한 범위를 넘어서는 것을 방지하기 위해 러스트는 슬라이스 생성 시점에 boundary check(경계 검사)를 수행한다. 이를 통해 프로그래머가 의도하지 않게 메모리 외부를 읽거나 쓰는 문제를 사전에 차단해 준다. 또한 슬라이스는 소유권을 가져가지 않기 때문에, 원본 데이터와 슬라이스 사이에 메모리 사용 충돌이 발생하지 않도록 빌림 규칙을 지키는 선에서 동시에 여러 슬라이스가 존재할 수 있다.

문자열 슬라이스(\&str)는 자주 볼 수 있는 형태로, 함수 파라미터에서 \&String 대신 \&str을 요구하는 경우가 많다. 이는 \&str이 더 일반적인 슬라이스 타입이기 때문이다. String, 문자열 리터럴(&'static str), 혹은 다른 문자열 슬라이스 모두를 \&str로 받아들일 수 있어 범용적으로 쓰기 편리하다. 따라서 러스트 표준 라이브러리나 여러 라이브러리에서 문자열 파라미터로 \&str을 사용하는 것을 흔히 볼 수 있다.

슬라이스는 러스트에서 안전한 부분 참조를 가능하게 하는 핵심 도구다. 문자열, 배열, 벡터 등 연속된 데이터에 대한 인덱싱 범위를 협소하게 가져감으로써, 필요한 부분만 빌림하여 쓸 수 있고, 이 과정에서 데이터 소유권이 이동하지 않으므로 클로닝에 따른 메모리 낭비를 피할 수 있다. 또한 빌림 규칙과 경계 검사를 통해 오류나 보안 취약점을 사전에 예방한다. 이런 특징 덕분에 러스트는 성능과 안전성을 동시에 만족시키는 언어로 자리매김할 수 있었고, 슬라이스는 그 중요한 기둥 중 하나라 할 수 있다.
