6.1 공통 컬렉션: Vec, String, HashMap
Vec<T>: 동적 배열
Vec<T>는 JavaScript의 Array에 해당하는 동적 크기 배열입니다. 힙에 할당되며, 크기가 런타임에 변경됩니다.
생성
fn main() {
// 빈 Vec 생성
let v1: Vec<i32> = Vec::new();
// 타입 추론 가능하면 생략
let mut v2 = Vec::new();
v2.push(1); // 컴파일러가 Vec<i32>로 추론
// vec! 매크로로 초기값 지정
let v3 = vec![1, 2, 3, 4, 5];
// 특정 크기와 초기값으로
let v4: Vec<u8> = vec![0; 32]; // [0, 0, 0, ..., 0] (32개)
// 범위에서 생성
let v5: Vec<i32> = (0..10).collect(); // [0, 1, 2, ..., 9]
// 다른 컬렉션에서 변환
let v6: Vec<String> = vec!["a", "b", "c"]
.iter()
.map(|s| s.to_string())
.collect();
}
TypeScript와 비교:
const v1: number[] = [];
const v2 = new Array<number>();
const v3 = [1, 2, 3, 4, 5];
const v4 = new Array(32).fill(0);
const v5 = Array.from({ length: 10 }, (_, i) => i);
읽기와 수정
fn main() {
let mut v = vec![10, 20, 30, 40, 50];
// 인덱스 접근 — 범위 초과 시 panic!
println!("{}", v[0]); // 10
println!("{}", v[4]); // 50
// 안전한 접근 — Option 반환
println!("{:?}", v.get(2)); // Some(30)
println!("{:?}", v.get(100)); // None (panic 없음)
// 수정
v[0] = 100;
println!("{:?}", v); // [100, 20, 30, 40, 50]
// 추가
v.push(60);
println!("{:?}", v); // [100, 20, 30, 40, 50, 60]
// 마지막 원소 제거
let last = v.pop(); // Some(60)
println!("{:?}", last);
// 특정 위치에 삽입 (O(n) 비용)
v.insert(1, 15); // 인덱스 1에 15 삽입
println!("{:?}", v); // [100, 15, 20, 30, 40, 50]
// 특정 위치 제거 (O(n) 비용)
let removed = v.remove(1); // 인덱스 1 제거
println!("Removed: {}", removed); // 15
// 마지막과 교환 후 제거 (O(1), 순서 바뀜)
let swapped = v.swap_remove(0);
println!("Swap-removed: {}", swapped); // 100
}
반복
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 불변 참조로 반복
for item in &v {
println!("{}", item);
}
// v는 여전히 유효
// 인덱스와 함께
for (i, item) in v.iter().enumerate() {
println!("[{}] = {}", i, item);
}
// 소유권 이동으로 반복 (v는 이후 사용 불가)
for item in v {
println!("{}", item);
}
// println!("{:?}", v); // 에러! v는 소비됨
// 가변 참조로 수정하며 반복
let mut v2 = vec![1, 2, 3];
for item in &mut v2 {
*item *= 2; // 역참조로 값 수정
}
println!("{:?}", v2); // [2, 4, 6]
}
유용한 메서드
fn main() {
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
// 길이와 비어있는지
println!("len: {}", v.len()); // 10
println!("empty: {}", v.is_empty()); // false
// 정렬
v.sort();
println!("{:?}", v); // [1, 1, 2, 3, 3, 4, 5, 5, 6, 9]
// 역순 정렬
v.sort_by(|a, b| b.cmp(a));
println!("{:?}", v); // [9, 6, 5, 5, 4, 3, 3, 2, 1, 1]
// 커스텀 정렬 (key 기반)
let mut words = vec!["banana", "apple", "cherry"];
words.sort_by_key(|s| s.len());
println!("{:?}", words); // ["apple", "banana", "cherry"]
// 중복 제거 (정렬 후)
v.sort();
v.dedup();
println!("{:?}", v); // [1, 2, 3, 4, 5, 6, 9]
// 검색
println!("{:?}", v.contains(&5)); // true
println!("{:?}", v.iter().position(|&x| x == 5)); // Some(4)
// 분할
let (left, right) = v.split_at(3);
println!("{:?} | {:?}", left, right);
// 연결
let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let combined: Vec<i32> = a.into_iter().chain(b.into_iter()).collect();
println!("{:?}", combined);
// 확장
let mut c = vec![1, 2, 3];
c.extend([4, 5, 6]);
println!("{:?}", c);
// 잘라내기 (길이 제한)
c.truncate(4);
println!("{:?}", c); // [1, 2, 3, 4]
// 전체 지우기
c.clear();
println!("{:?}", c); // []
}
블록체인에서의 Vec 활용
struct MerkleTree {
leaves: Vec<String>, // 트랜잭션 해시들
}
impl MerkleTree {
fn new(transactions: Vec<String>) -> Self {
MerkleTree { leaves: transactions }
}
fn root(&self) -> Option<String> {
if self.leaves.is_empty() {
return None;
}
let mut current = self.leaves.clone();
while current.len() > 1 {
let mut next = Vec::new();
// 두 개씩 묶어서 해시
for chunk in current.chunks(2) {
let combined = match chunk {
[left, right] => format!("{}{}", left, right),
[left] => left.clone(), // 홀수 개면 마지막은 그대로
_ => unreachable!(),
};
next.push(hash(&combined));
}
current = next;
}
current.into_iter().next()
}
}
fn hash(s: &str) -> String {
format!("{:x}", s.len()) // 실제로는 SHA-256
}
String: UTF-8 문자열
String은 힙에 할당된 가변 UTF-8 문자열입니다.
생성과 변환
fn main() {
// 생성 방법들
let s1 = String::new();
let s2 = String::from("hello");
let s3 = "hello".to_string();
let s4 = "hello".to_owned(); // to_string()과 동일
// 숫자 → 문자열
let n = 42;
let s5 = n.to_string();
let s6 = format!("{}", n);
// 문자열 → 숫자
let parsed: Result<i32, _> = "42".parse();
let num: i32 = "42".parse().unwrap();
}
이어붙이기
fn main() {
// push_str: 문자열 추가
let mut s = String::from("Hello");
s.push_str(", World");
s.push('!');
println!("{}", s); // "Hello, World!"
// + 연산자 (s1의 소유권이 이동됨!)
let s1 = String::from("Hello");
let s2 = String::from(", World!");
let s3 = s1 + &s2; // s1은 더 이상 유효하지 않음
println!("{}", s3);
// format! (소유권 이동 없음, 권장)
let s1 = String::from("Hello");
let s2 = String::from(", World!");
let s3 = format!("{}{}", s1, s2);
println!("{} {}", s1, s2); // s1, s2 모두 유효
println!("{}", s3);
}
인덱싱과 슬라이싱
fn main() {
let s = String::from("hello");
// 인덱스로 접근 불가! (UTF-8 때문)
// let c = s[0]; // 에러!
// 바이트 슬라이스 (ASCII는 OK, 멀티바이트 문자는 위험)
let slice = &s[0..3]; // "hel" (바이트 단위)
// 문자 단위 반복
for c in s.chars() {
print!("{} ", c); // h e l l o
}
// 바이트 단위 반복
for b in s.bytes() {
print!("{} ", b); // 104 101 108 108 111
}
// 한글 처리
let korean = String::from("안녕하세요");
println!("len (bytes): {}", korean.len()); // 15 (한글 = 3바이트)
println!("chars: {}", korean.chars().count()); // 5 (문자 수)
// 첫 번째 문자
let first: Option<char> = korean.chars().next();
println!("{:?}", first); // Some('안')
// n번째 문자 (O(n))
let third: Option<char> = korean.chars().nth(2);
println!("{:?}", third); // Some('하')
}
주요 String 메서드
fn main() {
let s = String::from(" Hello, World! ");
// 공백 제거
println!("{}", s.trim()); // "Hello, World!"
println!("{}", s.trim_start()); // "Hello, World! "
println!("{}", s.trim_end()); // " Hello, World!"
// 대소문자
println!("{}", s.trim().to_uppercase()); // "HELLO, WORLD!"
println!("{}", s.trim().to_lowercase()); // "hello, world!"
// 검색
println!("{}", s.contains("World")); // true
println!("{}", s.starts_with(" Hello")); // true
println!("{}", s.ends_with("! ")); // true
println!("{:?}", s.find("World")); // Some(9)
// 분리
let csv = "Alice,Bob,Carol";
let names: Vec<&str> = csv.split(',').collect();
println!("{:?}", names); // ["Alice", "Bob", "Carol"]
// 줄 분리
let text = "line1\nline2\nline3";
for line in text.lines() {
println!("{}", line);
}
// 교체
let replaced = "hello world".replace("world", "Rust");
println!("{}", replaced); // "hello Rust"
// 반복
let repeated = "abc".repeat(3);
println!("{}", repeated); // "abcabcabc"
// 문자 확인
println!("{}", "123".chars().all(|c| c.is_ascii_digit())); // true
println!("{}", "abc".chars().all(|c| c.is_alphabetic())); // true
// 분할 후 수집
let words: Vec<&str> = "one two three".split_whitespace().collect();
println!("{:?}", words); // ["one", "two", "three"]
}
HashMap<K, V>: 키-값 저장소
HashMap<K, V>는 JavaScript의 Map에 해당합니다.
생성과 삽입
use std::collections::HashMap;
fn main() {
// 빈 HashMap 생성
let mut scores: HashMap<String, u64> = HashMap::new();
// 삽입
scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"), 200);
scores.insert(String::from("Carol"), 150);
// 리터럴로 생성 (collect 이용)
let map: HashMap<&str, i32> = [
("one", 1),
("two", 2),
("three", 3),
].iter().cloned().collect();
// Rust 1.56+ 방법
let map2 = HashMap::from([
("Alice", 100),
("Bob", 200),
]);
println!("{:?}", scores);
}
읽기
use std::collections::HashMap;
fn main() {
let mut map = HashMap::from([
(String::from("Alice"), 100u64),
(String::from("Bob"), 200u64),
]);
// 인덱스 접근 — 키가 없으면 panic!
// let score = map["Charlie"]; // panic!
// 안전한 접근 — Option 반환
let alice_score = map.get("Alice"); // Some(&100)
let charlie_score = map.get("Charlie"); // None
println!("{:?}", alice_score); // Some(100)
println!("{:?}", charlie_score); // None
// 참조 없이 값만 얻기
let score = map.get("Alice").copied(); // Option<u64> (Copy 타입이므로)
let score2 = map.get("Alice").cloned(); // Option<u64> (Clone으로)
// 키 존재 확인
println!("{}", map.contains_key("Alice")); // true
// 기본값으로 가져오기
let score = map.get("Charlie").copied().unwrap_or(0);
println!("{}", score); // 0
}
업데이트
use std::collections::HashMap;
fn main() {
let mut map: HashMap<String, u64> = HashMap::new();
// 1. 덮어쓰기
map.insert(String::from("Alice"), 100);
map.insert(String::from("Alice"), 200); // 기존 값 교체
println!("{:?}", map.get("Alice")); // Some(200)
// 2. 없을 때만 삽입 (entry API)
map.entry(String::from("Bob")).or_insert(150);
map.entry(String::from("Bob")).or_insert(999); // 이미 있으므로 무시
println!("{:?}", map.get("Bob")); // Some(150)
// 3. 기존 값 기반 업데이트
let text = "hello world hello rust world hello";
let mut word_count: HashMap<&str, u32> = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1; // 역참조로 값 수정
}
println!("{:?}", word_count);
// {"hello": 3, "world": 2, "rust": 1}
// 4. 조건부 업데이트
map.entry(String::from("Carol"))
.or_insert_with(|| 300); // 없을 때 클로저 실행
// 5. 삭제
map.remove("Alice");
println!("{}", map.contains_key("Alice")); // false
}
반복
use std::collections::HashMap;
fn main() {
let map = HashMap::from([
("Alice", 100),
("Bob", 200),
("Carol", 150),
]);
// 키-값 쌍 반복 (순서 불보장!)
for (name, score) in &map {
println!("{}: {}", name, score);
}
// 키만
for name in map.keys() {
println!("{}", name);
}
// 값만
for score in map.values() {
println!("{}", score);
}
// 정렬된 순서로 (BTreeMap 사용하거나 Vec으로 변환)
let mut sorted: Vec<(&str, &i32)> = map.iter().collect();
sorted.sort_by_key(|(k, _)| *k);
for (name, score) in sorted {
println!("{}: {}", name, score);
}
}
블록체인에서의 HashMap 활용
use std::collections::HashMap;
struct UTXO {
txid: String,
vout: u32,
amount: u64,
}
struct UTXOSet {
// txid:vout → UTXO
utxos: HashMap<String, UTXO>,
}
impl UTXOSet {
fn new() -> Self {
UTXOSet { utxos: HashMap::new() }
}
fn add(&mut self, utxo: UTXO) {
let key = format!("{}:{}", utxo.txid, utxo.vout);
self.utxos.insert(key, utxo);
}
fn spend(&mut self, txid: &str, vout: u32) -> Option<UTXO> {
let key = format!("{}:{}", txid, vout);
self.utxos.remove(&key)
}
fn get_balance(&self, address: &str) -> u64 {
self.utxos.values()
.filter(|u| u.txid.starts_with(address)) // 실제로는 주소 비교
.map(|u| u.amount)
.sum()
}
}
JavaScript Array/Map vs Rust Vec/HashMap 총 비교
// JavaScript
const arr = [1, 2, 3];
arr.push(4);
arr.pop();
arr[0];
arr.includes(2);
arr.indexOf(2);
arr.slice(0, 2);
arr.splice(1, 1);
arr.sort();
arr.reverse();
arr.find(x => x > 2);
arr.filter(x => x > 1);
arr.map(x => x * 2);
arr.reduce((acc, x) => acc + x, 0);
arr.forEach(x => console.log(x));
arr.some(x => x > 2);
arr.every(x => x > 0);
arr.flat();
arr.flatMap(x => [x, x * 2]);
const map = new Map();
map.set("key", "value");
map.get("key");
map.has("key");
map.delete("key");
map.size;
// Rust
let mut v = vec![1, 2, 3];
v.push(4);
v.pop();
v[0];
v.contains(&2);
v.iter().position(|&x| x == 2);
v[0..2].to_vec(); // 슬라이스 후 Vec으로
v.remove(1);
v.sort();
v.reverse();
v.iter().find(|&&x| x > 2);
v.iter().filter(|&&x| x > 1);
v.iter().map(|&x| x * 2);
v.iter().fold(0, |acc, &x| acc + x);
v.iter().for_each(|x| println!("{}", x));
v.iter().any(|&x| x > 2);
v.iter().all(|&x| x > 0);
v.iter().flatten();
v.iter().flat_map(|&x| vec![x, x * 2]);
let mut map = HashMap::new();
map.insert("key", "value");
map.get("key");
map.contains_key("key");
map.remove("key");
map.len();
요약
Vec<T>: 동적 배열,push/pop/insert/removeString: UTF-8 문자열, 인덱스 접근 불가 (chars()로 문자 단위 접근)HashMap<K, V>: 키-값 저장,entry()API로 조건부 삽입/업데이트- 컬렉션 반복:
&v(불변 참조),&mut v(가변 참조),v(소유권 이동) get()으로 안전하게 접근 (Option 반환), 인덱스는 panic 위험
다음 챕터에서 클로저를 배웁니다.