Chap.8 common-collections
러스트의 표준 라이브러리에는 컬렉션
이라 불리는 여러 개의 매우 유용한 데이터 구조들이 포함되어 있습니다.
대부분의 다른 데이터 타입들은 하나의 특정한 값을 나타내지만, 컬렉션은 다수의 값을 담을 수 있습니다.
내장된 배열(build-in array)와 튜플 타입과는 달리, 이 컬렉션들이 가리키고 있는 데이터들은 힙에 저장되는데, 이는 즉 데이터량이 컴파일 타임에 결정되지 않아도 되며 프로그램이 실행될 때 늘어나거나 줄어들 수 있다는 의미입니다.
각각의 컬렉션 종류는 서로 다른 용량과 비용을 가지고 있으며, 여러분의 현재 상황에 따라 적절한 컬렉션을 선택하는 것은 시간이 지남에 따라 발전시켜야 할 기술입니다.
이번 장에서는 러스트 프로그램에서 굉장히 자주 사용되는 세 가지 컬렉션에 대해 논의해 보겠습니다:
- 벡터(vector) 는 여러 개의 값을 서로 붙어 있게 저장할 수 있도록 해줍니다.
- 스트링(string) 은 문자(character)의 모음입니다.
String
타입은 이전에 다루었지만, 이번 장에서는 더 깊이 있게 이야기해 보겠습니다. - 해쉬맵(hash map 은 어떤 값을 특정한 키와 연관지어 주도록 해줍니다. 이는 맵(map) 이라 일컫는 좀더 일반적인 데이터 구조의 특정한 구현 형태입니다.
표준 라이브러리가 제공해주는 다른 종류의 컬렉션에 대해 알고 싶으시면, the documentation를 봐 주세요.
벡터
우리가 보게될 첫번째 콜렉션은 벡터
라고도 알려진 Vec<T>
입니다.
- 벡터는 메모리 상에 서로 이웃하도록 모든 값을 집어넣는 단일 데이터 구조 안에 하나 이상의 값을 저장하도록 해줍니다.
- 벡터는 같은 타입의 값만을 저장할 수 있습니다.
새 벡터 만들기
비어있는 새 벡터를 만들기 위해서는, 아래의 Listing 8-1과 같이 Vec::new
함수를 호출해 줍니다:
let v: Vec<i32> = Vec::new();
- 여기에 타입 명시(type annotation)를 추가한 것을 주목하세요.
- 이 벡터에 어떠한 값도 집어넣지 않았기 때문에, 러스트는 우리가 저장하고자 하는 요소의 종류가 어떤 것인지 알지 못합니다.
- 벡터는 제네릭(generic)을 이용하여 구현되었습니다;
- 표준 라이브러리가 제공하는
Vec
타입은 어떠한 종류의 값이라도 저장할 수 있다는 것, - 그리고 특정한 벡터는 특정한 타입의 값을 저장할 때, 이 타입은 꺾쇠 괄호
(<>)
안에 적는다는 것만 알아두세요.
- 표준 라이브러리가 제공하는
- 일단 우리가 값을 집어넣으면 러스트는 우리가 저장하고자 하는 값의 타입을 대부분 유추할 수 있으므로, 좀 더 현실적인 코드에서는 이러한 타입 명시를 할 필요가 거의 없습니다.
- 초기값들을 갖고 있는
Vec<T>
을 생성하는 것이 더 일반적이며, 러스트는 편의를 위해vec!
매크로를 제공합니다. - 이 매크로는 우리가 준 값들을 저장하고 있는 새로운
Vec
을 생성합니다. 1
,2
,3
을 저장하고 있는 새로운Vec<i32>
을 생성할 것입니다:
let v = vec![1, 2, 3];
Listing 8-2: 값을 저장하고 있는 새로운 벡터 생성하기
초기 i32
값들을 제공했기 때문에, 러스트는 v
가 Vec
타입이라는 것을 유추할 수 있으며, 그래서 타입 명시는 필요치 않습니다.
벡터 갱신하기
벡터를 만들고 여기에 요소들을 추가하기 위해서 push
메소드를 사용할 수 있습니다:
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
Listing 8-3: push
메소드를 사용하여 벡터에 값을 추가하기
어떤 변수에 대해 그 변수가 담고 있는 값이 변경될 수 있도록 하려면, mut
키워드를 사용하여 해당 변수를 가변으로 만들어 줄 필요가 있습니다.
우리가 집어넣는 숫자는 모두 i32
타입이며, 러스트는 데이터로부터 이 타입을 추론하므로, 우리는 Vec<i32>
명시를 붙일 필요가 없습니다.
벡터를 드롭하는 것은 벡터의 요소들을 드롭시킵니다
struct
와 마찬가지로, Listing 8-4에 달려있는 주석처럼 벡터도 스코프 밖으로 벗어났을 때 해 제됩니다:
{
let v = vec![1, 2, 3, 4];
// v를 가지고 뭔가 합니다
}
// <- v가 스코프 밖으로 벗어났고, 여기서 해제됩니다
벡터의 요소들 읽기
지금까지 벡터를 만들고, 갱신하고, 없애는 방법에 대해 알아보았으니, 벡터의 내용물을 읽어들이는 방법을 알아보는 것이 다음 단계로 좋아보입니다. 벡터 내에 저장된 값을 참조하는 두 가지 방법이 있습니다.
{
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2);
}
Listing 8-5: 인덱스 문법 혹은 get
메소드를 사용하여 벡터 내의 아이템에 접근하기
두가지 세부사항을 주목하세요.
- 첫번째로, 인덱스값
2
를 사용하면 세번째 값이 얻어집니다:- 벡터는 0부터 시작하는 숫자로 인덱스됩니다.
- 두번째로, 세번째 요소를 얻기 위해 두 가지 다른 방법이 사용되었습니다:
&
와[]
를 이용하여 참조자를 얻은 것과,get
함수에 인덱스를 파라미터로 넘겨서Option<&T>
를 얻은 것입니다.
러스트가 벡터 요소를 참조하는 두가지 방법을 제공하는 이유는 여러분이 벡터가 가지고 있지 않은 인덱스값을 사용하고자 했을 때 프로그램이 어떻게 동작할 것인지 여러분이 선택할 수 있도록 하기 위해서입니다.
let v = vec![1, 2, 3, 4, 5];
let does_not_exist = &v[100];
let does_not_exist = v.get(100);
Listing 8-6: 5개의 요소를 가진 벡터에 100 인덱스에 있는 요소에 접근하기
-
이 프로그램을 실행하면, 첫번째의
[]
메소드는panic!
을 일으키는데, 이는 존재하지 않는 요소를 참조하기 때문입니다.이 방법은 여러분의 프로그램이 벡터의 끝을 넘어서는 요소에 접근하는 시도를 하면 프로그램이 죽게끔 하는 치명적 에러를 발생하도록 하기를 고려하는 경우 가장 좋습니다.
-
get
함수에 벡터 범위를 벗어난 인덱스가 주어졌을 때는 패닉 없이None
이 반환됩니다. 보통의 환경에서 벡터의 범위 밖에 있는 요소에 접근하는 것이 종종 발생한다면 이 방법을 사용할만 합니다. 여러분의 코드는 우리가 6장에서 본 것과 같이Some(&element)
혹은None
에 대해 다루는 로직을 갖추어야 합니다.