hyunsu blog

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

Rust Vec — 길이가 늘었다 줄었다 하는 목록

목차 · 4
  1. 01만들고 더한다
  2. 02for 로 돌고 고친다
  3. 03범위 밖 번호는 패닉한다
  4. 04실습 — get 은 Option 을 돌려준다

Rust 의 Vec 은 같은 타입 값을 담는, 길이가 바뀌는 목록이다. Vec::new() 나 vec![…] 로 만들고 push 로 더한다. v[n] 처럼 범위 밖 번호를 쓰면 실행 중에 패닉하고, get(n) 은 Option 을 돌려줘서 없는 경우를 안전하게 다룬다.

01 만들고 더한다

할 일 목록을 만들어 보자. 9편의 배열은 길이가 정해져 있어서 나중에 더할 수 없었다.

rust
fn main() {
    let mut todo = Vec::new();
    todo.push("wash");
    todo.push("cook");
    todo.push("read");

    println!("{todo:?}");
    println!("count: {}", todo.len());
    println!("first: {}", todo[0]);

    let nums = vec![3, 1, 2];
    println!("{nums:?}");
}
text
   Compiling todo v0.1.0 (~/projects/todo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/todo`
["wash", "cook", "read"]
count: 3
first: wash
[3, 1, 2]
  • Vec::new() 는 빈 Vec 을 만든다. 더할 것이니 let mut 다.
  • push 는 맨 뒤에 하나를 더한다. 처음 넣은 값이 &str 이라 이 Vec 의 타입이 정해진다. 다른 타입을 넣으면 오류다.
  • len()·todo[0]·{:?} 는 배열과 똑같이 쓴다.
  • vec![3, 1, 2] 는 값을 넣은 채로 만드는 매크로다. println! 처럼 느낌표가 붙는다.

02 for 로 돌고 고친다

rust
fn main() {
    let mut scores = vec![72, 85, 90];

    for s in &mut scores {
        *s = *s + 5;
    }

    for s in &scores {
        println!("{s}");
    }

    scores.sort();
    scores.reverse();
    println!("{scores:?}");
}
text
   Compiling todo v0.1.0 (~/projects/todo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
     Running `target/debug/todo`
77
90
95
[95, 90, 77]
  • for s in &mut scores 는 원소를 하나씩 고칠 수 있게 빌려 온다(19편). *s 는 "s 가 가리키는 값" 이다. 참조 자체가 아니라 그 값을 고치므로 * 를 붙인다.
  • for s in &scores 는 읽기만 하게 빌려 온다(18편). & 를 빼고 for s in scores 라고 쓰면 Vec 이 for 로 이동해 버려서(15편), 그 뒤에 scores 를 쓸 수 없다.
  • sort() 는 작은 것부터 늘어놓고, reverse() 는 순서를 뒤집는다.

03 범위 밖 번호는 패닉한다

9편의 배열에서는 범위 밖 번호를 컴파일러가 미리 막았다. Vec 은 길이가 실행 중에 바뀌니 미리 알 수 없다.

rust
fn main() {
    let todo = vec!["wash", "cook", "read"];
    let n = todo.len();
    println!("{}", todo[n]);
}
text
   Compiling todo v0.1.0 (~/projects/todo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/todo`

thread 'main' (213063879) panicked at src/main.rs:4:24:
index out of bounds: the len is 3 but the index is 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

길이가 3 인데 번호 3 을 썼다(index out of bounds). 번호는 0 부터라 마지막은 2 다. 컴파일은 됐지만 실행 중에 멈췄다.

04 실습 — get 은 Option 을 돌려준다

번호가 범위 안인지 확신할 수 없으면 get 을 쓴다.

rust
fn main() {
    let todo = vec!["wash", "cook", "read"];

    match todo.get(1) {
        Some(item) => println!("2nd: {item}"),
        None => println!("no 2nd"),
    }

    match todo.get(10) {
        Some(item) => println!("11th: {item}"),
        None => println!("no 11th"),
    }
}
text
   Compiling todo v0.1.0 (~/projects/todo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/todo`
2nd: cook
no 11th

todo.get(1) 은 값이 있으니 Some(…), todo.get(10) 은 없으니 None 이다. 26편의 Option 이라 match 로 두 경우를 나눴다. 패닉 없이 "없다" 를 다룬다. 27편의 if let 으로 바꿔 써 보자.

v[n]v.get(n)
돌려주는 것값Option
범위 밖이면패닉None
쓰는 때범위 안이 확실할 때확실하지 않을 때

다음 편은 이름으로 값을 찾는 목록, HashMap 이다.