hyunsu blog

홈강좌Rust 입문·중급 47편 중 21편째

Rust 슬라이스 — 배열과 문자열의 일부만 빌린다, 한글은 3바이트씩

목차 · 4
  1. 01배열의 일부만 빌린다
  2. 02문자열도 같은 방법으로 자른다
  3. 03글자 중간을 자르면 패닉한다
  4. 04실습 — 글자 단위로는 chars()

Rust 슬라이스는 &scores[1..4] 처럼 배열이나 문자열의 일부만 빌린 참조다. 문자열 슬라이스의 번호는 글자가 아니라 바이트다. 한 글자가 3바이트인 한글을 글자 중간에서 자르면 실행 중에 패닉한다. 글자 단위로 다룰 때는 chars() 를 쓴다.

01 배열의 일부만 빌린다

9편의 점수 배열에서 가운데 셋만 꺼내 보자.

rust
fn main() {
    let scores = [72, 85, 90, 64, 88];

    let middle = &scores[1..4];
    let first_two = &scores[..2];
    let last_two = &scores[3..];

    println!("{middle:?}");
    println!("{first_two:?}");
    println!("{last_two:?}");
}
text
   Compiling scores v0.1.0 (~/projects/scores)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/scores`
[85, 90, 64]
[72, 85]
[64, 88]
  • &scores[1..4] 는 1번부터 4번 바로 앞까지(1·2·3번)를 빌린다. 범위 쓰는 법은 14편과 같다.
  • 시작을 비우면(..2) 처음부터, 끝을 비우면(3..) 끝까지다.
  • 앞에 & 가 붙는 것은 18편의 빌림이라서다. 값을 복사하지 않고 원래 배열의 그 구간을 가리킨다.

02 문자열도 같은 방법으로 자른다

rust
fn main() {
    let english = "hello world";
    println!("{}", &english[0..5]);

    let korean = "안녕하세요";
    println!("{}", korean.len());
    println!("{}", &korean[0..3]);
    println!("{}", &korean[0..6]);
}
text
   Compiling text v0.1.0 (~/projects/text)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/text`
hello
15
안
안녕

영어 "hello world" 의 [0..5] 는 hello 다. 그런데 한글을 보면 이상하다.

  • "안녕하세요" 는 다섯 글자인데 len() 이 15 다.
  • [0..3] 이 안 한 글자, [0..6] 이 안녕 두 글자다.

문자열 번호가 글자가 아니라 바이트이기 때문이다. Rust 문자열은 UTF-8 로 저장되는데, 영어 글자는 1바이트, 한글 글자는 3바이트를 쓴다. 그래서 다섯 글자가 15바이트이고, 한 글자를 자르려면 3 단위로 잘라야 한다. 18편에서 len() 이 "정확히는 바이트 수" 라고 한 것이 이 말이다.

03 글자 중간을 자르면 패닉한다

영어처럼 [0..2] 로 두 글자를 잘라 보자.

rust
fn main() {
    let korean = "안녕하세요";
    let part = &korean[0..2];
    println!("{part}");
}
text
   Compiling text v0.1.0 (~/projects/text)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/text`

thread 'main' (213046763) panicked at src/main.rs:3:23:
end byte index 2 is not a char boundary; it is inside '안' (bytes 0..3) of `안녕하세요`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

컴파일은 됐지만 실행 중에 멈췄다(panicked). 둘째 줄이 이유다. 끝 번호 2 가 글자 경계가 아니다(not a char boundary). '안' 이 0~3 바이트를 차지하는데 그 한가운데를 잘랐다. 괄호 안의 숫자는 실행할 때마다 바뀌니 신경 쓰지 않아도 된다.

반쪽짜리 글자는 글자가 아니니, Rust 는 깨진 글자를 만드는 대신 멈춘다. 컴파일러가 미리 막지 못하는 것은 자르는 번호가 실행 중에 정해질 수 있기 때문이다.

04 실습 — 글자 단위로는 chars()

글자 수를 세거나 한 글자씩 다룰 때는 chars() 를 쓴다.

rust
fn main() {
    let korean = "안녕하세요";
    println!("{}", korean.chars().count());

    for c in korean.chars() {
        println!("{c}");
    }
}
text
   Compiling text v0.1.0 (~/projects/text)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/text`
5
안
녕
하
세
요

chars() 는 문자열을 char(7편) 하나씩으로 나눈다. .count() 를 붙이면 글자 수 5 가 나오고, for 로 돌면 한 글자씩 꺼낸다. 바이트를 따질 필요가 없다.

정리하면, 영어만 다루는 짧은 코드라면 [0..5] 로 잘라도 되지만, 한글이 섞일 수 있으면 chars() 로 글자 단위로 다룬다. 여기까지가 소유권 구간이다. 다음 편부터는 여러 값을 이름 붙여 묶는 구조체로, 데이터를 다루는 법을 본다.