[Rust]

[Rust] 컬렉션(Collection)

극꼼 2023. 8. 14. 22:55
반응형


<컬렉션(Collection)>

: 러스트 표준 라이브러리의 유용한 데이터 구조로, 다수의 값을 담을 수 있습니다. 배열이나 튜플과는 달리 힙에 저장되는데 이는 프로그램 실행 중에 데이터의 양이 늘거나 줄어들 수 있음을 의미합니다.

러스트 프로그램에서 자주 사용되는 컬렉션에는 다음 세가지가 있습니다.

  • 벡터(vector) : 여러 개의 값을 서로 붙어서 저장될 수 있게 해줍니다.
  • 문자열(string) : 문자(character)의 모음입니다.
  • 해시맵(hash map) : 어떤 값을 특정 키와 연관지어 주도록 해줍니다.

1) 벡터(vector) - Vec<T>

같은 타입의 값만 저장 가능하며, 모든 값을 메모리에서 연속적으로 배치합니다.

 

- 새 벡터 만들기 : 비어있는 벡터를 만들기 위해서 Vec::new 함수를 호출합니다. 벡터에 어떤 값도 집어넣지 않았기 때문에 저장하고자 하는 타입을 명시적으로 입력해줍니다. 값이 있는 벡터를 만들기 위해서는 러스트에서 제공하는 vec! 매크로를 사용할 수 있습니다.

let v: Vec<i32> = Vec::new(); // 비어있는 벡터
let v = vec![1, 2, 3]; // 값을 저장하는 벡터

 

- 벡터 업데이트 : push 메서드를 사용할 수 있습니다. push 메서드로 값을 추가하기 위해서는 가변 변수로 선언해주어야 합니다. 벡터 타입을 명시해주지 않았는데, push할 때 러스트가 v의 타입을 추론합니다.

let mut v = Vec::new();

v.push(5);
v.push(6);
v.push(7);
v.push(8);

 

- 벡터 요소 읽기 : 벡터에 저장된 값을 참조하는 방법은 인덱싱, get 메서드가 있습니다.

인덱싱을 사용할 때 벡터 범위에서 벗어난 인덱스를 사용할 경우 프로그램이 죽어버리기 때문에 주의해야 합니다.

get 함수를 사용할 경우 match를 통해 처리할 수 있는 Option<&T>를 얻게됩니다. 만약 벡터 범위에서 벗어난 인덱스로 값을 가져오려하면 None이 반환됩니다. 따라서 match에는 None을 처리하는 로직이 필요합니다. 

let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2]; // 인덱싱

let third: Option<&i32> = v.get(2); // get 메서드
match third {
    Some(third) => println!("The third element is {third}"),
    None => println!("There is no third element."),
}

 

- 벡터를 참조 -> 벡터를 업데이트 -> 이전에 참조한 변수 사용 순서로 작업하려 할 경우 : 벡터는 모든 요소가 서로 붙어서 메모리에 저장하는데, 새로운 값을 끝에 추가할 때 현재 벡터 메모리 위치에 새로운 요소를 추가할 공간이 없다면 다른 곳에 메모리를 새로 할당해서 값을 복사합니다. 따라서 벡터를 업데이트하면 기존의 참조는 사용할 수 없게 됩니다.

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0]; // 불변 변수 first가 v의 0번 값 참조
v.push(6); // v 업데이트
println!("{first}"); // 여기서 에러 발생

 

- 벡터 값 반복 : 벡터 내의 각 요소에 차례대로 접근하기 위한 반복 처리로, for 루프를 사용할 수 있습니다.

* 는 역참조 연산자로, 이후에 배워보겠습니다.

let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

 

- 열거형으로 여러 타입 저장하기 : 벡터에는 같은 타입의 값만 저장할 수 있는데, 열거형을 사용해 여러 타입을 저장할 수 있습니다. 대신 벡터의 각 요소에 수행되는 연산은 match 표현식과 함께 사용해야 합니다.

enum Cell {
    Int(i32),
    Float(f64),
    Text(String),
}

let mut row = Vec::new();
row.push(Cell::Int(3));
row.push(Cell::Text(String::from("blue")));
row.push(Cell::Float(10.12));

 

벡터는 struct와 마찬가지로 선언된 스코프를 벗어날 때 해제됩니다.


2) 문자열(string)

: UTF-8로 인코딩 되어 다른 어딘가에 저장된 문자열 데이터의 참조자입니다.

* String, str(문자열 슬라이스)는 모두 UTF-8로 인코딩 되어 있음.

 

- 새로운 문자열 생성 : String은 벡터에 더해서 몇 가지 보장, 제한, 기능을 추가한 wrapper로 구현되어 있습니다. 따라서 Vec<T>에서 쓸 수 있는 연산 다수가 String에서도 똑같이 쓸 수 있습니다. String도 새 인스턴스를 생성하기 위해 new 함수를 사용할 수 있습니다.

let mut s1 = String::new(); // 생성자

let s2 = "hello, world".to_string();
let s3 = String::from("hello, world"); // s2, s3는 동일

 

- 문자열 업데이트 : push_str로 문자열 슬라이스를 추가할 수 있습니다. push_str 메서드는 매개변수의 소유권을 가져오지 않습니다.

let mut s1 = String::new();
s1.push_str("happy");

let s2 = "cat";
s1.push_str(s2);
println!("{s2}"); // cat 출력

 

* + 연산자나 format! 매크로도 사용할 수 있습니다.

  • + 연산자 : s3 = s1 + &s2 구문에서 s3는 s1의 소유권을 가져다가 s2 복사본을 추가한 후 반환한 소유권을 가져갑니다. +연산자의 가장 왼쪽에 있는 s1은 반드시 s3에게 소유권을 넘겨주는 형태여야 합니다.
let s1 = String::from("happy");
let s2 = String::from("cat");

//let s3 = &s1 + &s2; // error[E0369]: cannot add `&String` to `&String`

let s3 = s1 + &s2; // s1의 소유권이 s3로 옮겨감
println!("{s1}"); // error[E0382]: borrow of moved value: `s1`
  • format! 매크로 : 어떤 매개변수의 소유권도 가져가지 않습니다.
let s1 = String::from("happy");
let s2 = String::from("cat");

let s3 = format!("{s1} {s2}");
println!("{s3}"); // happy cat 출력

 

- 인덱싱 : 러스트에서 인덱싱 문법으로 String 에 접근하고자 하면 에러가 발생합니다. 여기엔 다음과 같은 이유가 존재합니다.

let s1 = String::from("happy");
let h = s1[0]; // error[E0277]: the type `String` cannot be indexed by `{integer}`

1) 러스트의 관점에서 문자열을 보는 방식에는 바이트, 스칼라 값, 문자소 클러스터(grapheme cluster)가 있습니다. n번 인덱스를 출력하라는 요구를 받았을 때 러스트는 셋 중 무엇을 리턴해야 하는지 명확하지 않게 됩니다.

 

힌디어 ‘नमस्ते’를 예시로 살펴보자면 이렇습니다.

바이트 벡터 : [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
스칼라 벡터 : ['न', 'म', 'स', '्', 'त', 'े']
문자소 클러스터 : ["न", "म", "स्", "ते"]

문자열을 UTF-8 인코딩할 때 각각의 유니코드 스칼라 값이 항상 저장소의 1 바이트를 차지하지 않기 때문에 다음 예시에서 hello 문자열 벡터는 각각 4, 24바이트를 차지합니다.

let hello = String::from("Hola"); // 4바이트
let hello = String::from("Здравствуйте"); // 24바이트

2) 인덱스 연산은 언제나 상수 시간(O(1))에 실행될 것으로 기대되는데, 러스트는 문자열 내에 유효한 문자가 몇개인지 알아내기 위해 시작 지점부터 인덱스로 지정된 곳까지 훑기 때문에 그런 성능을 보장하는건 불가능합니다.

 

- 문자열 접근 방법 : Range, chars(), bytes()가 있습니다.

let s1 = String::from("happy");
let s2 = &s1[0..=1];

println!("{s2}"); // ha 출력

for c in s1.chars() {
    println!("{c}");  // З д // h a p p y 각각 출력
}

for b in s1.bytes() {
    println!("{b}");  // 104 97 112 112 121 각각 출력
}

 

n번째 글자에 접근하기 위해서 nth() 함수를 쓸 수도 있습니다. Option<T> 를 반환합니다. 시간 복잡도는 O(n) 입니다.

let s1 = String::from("happy");

let s2 = s1.chars().nth(2);
println!("{:?}", s2); // Some('p') 출력

3) 해시맵(hash map) - HashMap<K, V>

: K는 키, V는 값을 의미하며, 해시 함수(hashing function)를 사용해서 키와 값을 매핑한 것을 메모리 어디에 저장할지를 결정합니다.

 

- 해시맵 생성 : 표준 라이브러리의 컬렉션에서 HashMap을 먼저 가져와야 합니다. 그 다음 new를 사용해서 생성하고, insert로 요소를 추가합니다.

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

 

- 해시맵 값 접근 : get 메서드. copied를 호출해서 참조가 아닌 Option<i32>를 가져오고, unwrap_or를 써서 scores 해시맵이 해당 키에 대한 아이템을 가지고 있지 않다면 0을 설정하도록 합니다.

let score = scores.get(&team_name).copied().unwrap_or(0);

for (key, value) in &scores {
    println!("{key}: {value}");
}
// Yellow: 50
// Blue: 10

 

- 소유권 : Copy 트레이트를 구현한 타입의 값은 복사되고, String처럼 소유권이 있는 값은 해시맵이 그 값의 소유자가 됩니다.

use std::collections::HashMap;

fn main(){
    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // field_name과 field_value는 이 시점부터 유효하지 않음
}

 

- 해시맵 업데이트 : 특정 키에 대한 값을 변경하고 싶을 때

scores.insert(String::from("Blue"), 10);

scores.insert(String::from("Blue"), 25); // 그냥 덮어쓰기
scores.entry(String::from("Blue")).or_insert(50); // 해당 키가 없을 때만 추가

요약 참조 링크 : https://rust-kr.github.io/doc.rust-kr.org/ch08-00-common-collections.html

반응형

'[Rust]' 카테고리의 다른 글

[Rust] 에러 처리  (0) 2023.08.16
[Rust] match 연산자  (0) 2023.08.15
[Rust] 패키지, 크레이트, 모듈로 프로젝트 관리  (0) 2023.08.11
[Rust] 열거형(enum)  (0) 2023.08.01
[Rust] 구조체 - 메서드  (0) 2023.07.30