일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 그리디
- 코딩
- API
- 코드
- 애자일
- Spring
- 개발자
- 데이터베이스
- cleancode
- 읽기쉬운코드
- 클린코드
- framework
- 알고리즘
- 스프링
- 개발
- 그리디알고리즘
- 애자일프로그래밍
- Java
- spring boot
- 애자일기법
- 백준
- 코딩테스트
- 엘라스틱서치
- database
- Baekjoon
- JPA
- 자바
- 프레임워크
- Elasticsearch
- ES
- Today
- Total
튼튼발자 개발 성장기🏋️
러스트 18 - Collections #1 : Vector 본문
언제나 그렇듯 대부분의 언어는 컬렉션이라 불리는 데이터 구조를 제공한다.
우리가 공부했던 <변수와 타입 #2>튜플과 배열과는 다르게, 힙 영역에 저장된다.
즉, Compile Time에 데이터 크기를 알 필요가 없다는 뜻이 되며, 크기 확장 혹은 축소가 가능하다는 이야기다.
(우리가 공부할 각 컬렉션의 성격이 조금씩 다르니, 적절하게 사용할 수 있는 능력을 기르는 것이 옳다고 생각된다.)
이번 장에서 다룰 컬렉션은 Vector(벡터)다.
- 메모리상에서 이웃되도록 모든 값을 넣는 단일 데이터 구조에 하나 이상의 값을 저장할 수 있도록 한다.
- 같은 타입의 값만을 저장 가능하다.
벡터 정의 및 사용
fn main() {
// vector 정의
let mut vector: Vec<i32> = Vec::new();
// vector에 값 삽입
vector.push(1);
vector.push(2);
vector.push(3);
// vector 출력
println!("vector : {:?}", vector);
// vector에서 값 제거
vector.pop();
println!("vector : {:?}", vector);
vector.pop();
println!("vector : {:?}", vector);
vector.pop();
println!("vector : {:?}", vector);
}
처음 언급한 바와 같이, i32타입을 저장할 것이라는 타입 명시만 있을 뿐, 크기는 지정하지 않았다.
사실 코드를 더 줄일 수 있다.
fn main() {
// vector 정의
let mut vector = vec![1, 2, 3];
// vector 출력
println!("vector : {:?}", vector);
// vector에서 값 제거
vector.pop();
println!("vector : {:?}", vector);
vector.pop();
println!("vector : {:?}", vector);
vector.pop();
println!("vector : {:?}", vector);
}
러스트는 편리함을 제공해주기 위해 매크로를 제공한다. 같은 역할을 하는 코드지만, 상당히 축소된 것을 볼 수 있다.
이번엔 특정 값에 접근하는 방법 두 가지를 다뤄보자.
fn main() {
// vector 정의
let vector = vec![1, 2, 3];
// vector 출력
println!("vector : {:?}", vector);
// vector 특정 값 접근 #1
let var1: &i32 = &vector[2];
// let var1: &i32 = &vector[100]; // Error 발생!!!
println!("var1 : {}", var1);
// vector 특정 값 접근 #2
let var2: Option<&i32> = vector.get(2);
// let var2: Option<&i32> = vector.get(100); // None 반환
println!("var2 : {:?}", var2);
}
[idx]를 이용한 방법(#1)과 Vec.get(int idx)를 이용한 방법(#2)이 있다.
#1의 경우 out of index가 발생할 경우 error를 떨구기 때문에, 좋지 않은 접근 방법이라고 볼 수 있다.
#2의 경우 None이 리턴되어 어떻게든 비벼볼 수 있는 상황이 된다.
즉, 항상, index로 무언가 접근할 경우 None을 적극적으로 활용하여 ERROR발생을 최소화 하자!!!
<러스트 13 - 구조체 #1>에서 다루었던 Borrowing에 대해 기억이 나지 않아서 다시 공부하고 왔다... 왜냐하면 지금 다시 나올 예정이니까...ㅎㅎㅎㅎㅎ
프로그램이 유효한 레퍼런스를 가지고 있을 경우 borrow checker는 이 레퍼런스와 벡터 내용에 대한 다른 레퍼런스가 유효하도록 소유권 및 빌림 규칙을 시행한다.
상위 코드의 #1방법으로 접근 한 뒤, vector를 사용해보자.
fn main() {
// vector 정의
let mut vector = vec![1, 2, 3];
// vector 출력
println!("vector : {:?}", vector);
// vector 특정 값 접근 #1
let var1: &i32 = &vector[2];
// let var1: &i32 = &vector[100]; // Error 발생!!!
println!("var1 : {}", var1);
vector.push(99);
// vector 특정 값 접근 #2
// let var2: Option<&i32> = vector.get(2);
// let var2: Option<&i32> = vector.get(100); // None 반환
// println!("var2 : {:?}", var2);
}
빌림을 다루는 장에서 이것저것 많은 것을 해보면 이 에러 메시지를 많이 보았을 것 같다.
이 메시지는 rust가 "야 너 이거 빌려줘놓고 왜 사용하려고 해? 안돼. 쓰지마."라고 이야기 해주는 것.
for문을 이용한 벡터의 모든 값 접근.
fn main() {
let mut vector = vec![1, 2, 3, 50, 100];
// 벡터의 값 일정하게 변경
for i in &mut vector {
*i *= 3;
}
// 각 값 출력 #1
println!("vector : {:?}", vector);
// 각 값 출력 #2
// for i in &vector {
// println!("vector : {}", i);
// }
}
(개인적으로 출력에 대해서는 #1이 내 입맛에 맞다.
#2의 경우, 출력 만을 위해 for문이 들어가는 것이 맘에 들지 않는다. 만일, for문 안에서 값 변경이나 혹은 특정한 로직이 시행 된 후에 출력을 함께 하는 것이라면 #2가 맞지만, 출력만을 위해서 존재하는 것이라면 #1이 맞다고 본다.)
구조체와 같은 데이터 구조와 달리, 같은 타입의 데이터만을 저장할 수 있는 벡터는 불편하게 느낄 수 있을지 모르겠다.
우리는 어렵게 다루었던 열거형을 이용하여 이 제약을 풀 수 있을지 모른다!
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
정수와 실수, 문자열을 담을 수 있게 되었다.
이와 같이 러스트가 Compile Time에 벡터 내에 저장되는 타입을 알아야할 필요가 있기 때문에 각 요소를 저장하기 위해 힙 메모리 영역이 얼마나 필요한지 알 수 있다.
(코드에서 이 벡터가 어떤 유형을 담는지 명시적으로 알 수 있다는 이점도 있다.)
'프로그래밍 > RUST' 카테고리의 다른 글
러스트 20 - 예외처리 #1 : panic! (0) | 2019.04.14 |
---|---|
러스트 19 - Collections #2 : Hash Map (0) | 2019.03.31 |
러스트 17 - if let (0) | 2019.03.30 |
러스트 16 - 열거형 #2 (0) | 2019.03.24 |
러스트 15 - 열거형 #1 (0) | 2019.03.24 |