[Rust] Vector

silver·2023년 4월 17일
0

벡터는 메모리 상에 서로 이웃하도록 모든 값을 집어넣는 단일 데이터 구조 안에 하나 이상의 값을 저장하도록 해준다. 벡터는 같은 타입의 값만을 저장할 수 있다.

새 벡터 만들기

let v: Vec<i32> = Vec::new();

여기에 타입 명시(type annotation)를 추가한 것을 주목해야 한다. 이 벡터에 어떠한 값도 집어넣지 않았기 때문에, 러스트는 우리가 저장하고자 하는 요소의 종류가 어떤 것이지 알지 못하기 때문에 타입 명시를 해주었다.

초기값들을 갖고 있는 Vec<T>를 생성하는 것이 더 일반적이며, 러스틑 편의를 위해 vec! 매크로를 제공한다. 이 매크로는 우리가 준 값들을 저장하고 있는 새로운 Vec 를 생성한다.

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

초기 i32 값들을 제공했기 때문에, 러스트는 v가 Vec 타입이라는 것을 유추할 수 있으며, 그래서 타입 명시는 필요하지 않다.

벡터 갱신하기

벡터를 만들고 여기에 요소들을 추가하기 위해서는 push 메소드를 사용할 수 있다.

let mut v = Vec::new();

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

어떤 변수에 대해 그 변수가 담고 있는 값이 변경될 수 있도록 하려면, mut 키워드를 사용하여 해당 변수를 가변으로 만들어주어야 한다. 우리가 집어넣은 숫자는 모두 i32 타입이며, 러스트는 데이터로부터 이 타입을 추론하므로, 우리는 Vec<i32> 명시를 붙일 필요가 없다.

벡터를 드롭하는 것은 벡터의 요소들을 드롭시킨다

벡터도 스코프 밖으로 벗어났을 때 해제된다.

{
	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);

첫번째로, 인덱스 값 2 를 이용하여 세번째 값이 얻어진다.
두번째로, 세번째 요소를 얻기위해 두 가지 다른 방법이 사용되었다.
&[] 를 이용하여 참조자를 얻은 것과, get 함수에 인덱스를 파라미터로 넘겨서 Option<&T> 를 얻은 것이다.

러스트가 벡터 요소를 참조하는 두가지 방법을 제공하는 이유는, 벡터가 가지고 있지 않은 인덱스 값을 사용하고자 했을 때 프로그램이 어떻게 동작할 것인지를 선택할 수 있도록 하기 위함이다.

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

let does_not_exist = &v[100];
let does_not_exist = v.get(100);

첫번째의 [] 메소드는 panic! 을 일으키는데, 이는 존재하지 않는 요소를 참조하기 때문이다. 이 방법은 벡터의 끝을 넘어서는 요소에 접근하는 시도를 하면 프로그램이 죽게끔 하는 치명적 에어를 발생하도록 하기를 고려하는 경우 가장 좋다.

get 함수에 벡터 범위를 벗어나는 인덱스가 주어졌을 때 패닉 없이 None 이 반환된다. 보통의 환경에서 벡터의 범위 밖에 있는 요소에 접근하는 것이 종종 발생한다면 이 방법을 사용할만 하다.

유효하지 않은 참조자

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

let first = &v[0];

v.push(6);

아래 에러 발생

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as
immutable
  |
4 | let first = &v[0];
  |              - immutable borrow occurs here
5 |
6 | v.push(6);
  | ^ mutable borrow occurs here
7 | }
  | - immutable borrow ends here

새로운 요소를 벡터의 끝에 추가하는 것은 새로 메모리를 할당하여 예전 요소를 새 공간에 복사하는 일이 필요로 할 수 있는데, 이는 벡터가 모든 요소들을 붙여서 저장할 공간이 충분치 않는 환경에서 일어날 수 있다. 이러한 경우, 첫번째 요소에 대한 참조자는 할당이 해제된 메모리를 가르키게 될 것이다. 빌림 규칙은 프로그램이 이러한 상황에 빠지지 않도록 해준다.

벡터 내의 값들에 대한 반복처리

만일 벡터 내의 각 요소들을 차례대로 접근하고 싶다면, 하나의 값에 전근하기 위해 인덱스를 사용하는 것 보다는, 모든 요소들에 대한 반복처리를 할 수 있다.

let v = vec![100, 32, 57];
for i in &v {
	println!("{}", i);
}

for 루프를 사용하여 i32 의 벡터 내에 있는 각 요소들에 대한 불변 참조자를 얻어서 이를 출력하는 방법을 보여준다.

만일 모든 요소들을 변형시키길 원한다면 가변 벡터 내의 각 요소에 대한 가변 참조자로 반복잡업을 할 수 도 있다.

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

가변 참조자가 참고하고 있는 값을 바꾸기 위해서, i에 +=연산자를 이용하기 전에 역참조 연산자(*)를 사용하여 값을 얻어야 한다.

열거형을 사용하여 여러 타입을 저장하기

시작 부분에서, 벡터는 같은 타입을 가진 값들만 저장할 수 있다고 했다. 하지만 다른 타입의 값들에 대한 리스트를 저장할 필요한 상황이 분명히 있다. 다행히도, 열거형의 variant는 같은 열거형 타입 내에 정의가 되므로, 벡터 내에 다른 타입의 값들을 저장할 필요가 있다면 열거형 정의를 사용할 수 있다.

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

let row = vec![
	SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

러스트가 컴파일 타임에 벡터 내에 저장될 타입이 어떤 것인지 알아야할 필요가 있는 이유는 각 요소를 저장하기 위해 얼만큼의 힙 메모리가 필요한지 알기 위함이다. 부차적인 이점은 이 벡터에 허용되는 타입에 대해 명시적일 수 있다는 점이다.

참고: https://rinthel.github.io/rust-lang-book-ko/ch08-01-vectors.html

0개의 댓글