A Vec grows by reallocating a bigger buffer and moving every element into it once its capacity runs out — which means a reference taken into a Vec before a push can be left pointing at freed memory after one. In C or C++ this is a real, silent use-after-free. Rust's borrow checker refuses to let it happen at all: holding an immutable reference into a Vec while also pushing to it (a mutable borrow) is a compile error, not a runtime bug waiting to be found.
let mut v = vec![1, 2, 3];
let first = &v[0]; // a reference into v's current backing buffer
v.push(4); // may reallocate -- would leave `first` dangling
println!("{}", first);
// error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutableVec<T> is a growable list, storing its elements on the heap; String is a growable, heap-allocated, UTF-8-encoded piece of text (distinct from &str, a borrowed string slice); HashMap<K, V> stores key-value pairs with average O(1) lookup. All three grow at runtime, unlike Rust's fixed-size arrays.
Vec<T>: a growable list
Unlike an array, a Vec's length isn't part of its type — it can grow and shrink at runtime, backed by a reallocating heap buffer similar in spirit to Go's slices.
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3);
let v2 = vec![1, 2, 3]; // macro shorthand, equivalent to the above
println!("{}", v2[0]); // 1 — panics if out of bounds
println!("{:?}", v2.get(10)); // None — safe alternative, no panicThe bug above is refused for exactly this reason: a reference taken from a Vec is only valid as long as nothing mutates the Vec out from under it. The fix is to stop holding a reference across the mutation — copy the value out instead, since a plain i32 is cheap to copy and doesn't need to stay borrowed:
let mut v = vec![1, 2, 3];
let first = v[0]; // copy the value out instead of borrowing it
v.push(4); // now fine -- nothing refers into v's buffer
println!("{}", first);
// 1String vs. &str
String owns its data and can grow; &str is a borrowed view (a slice) into string data owned by something else — often a string literal or a String. Function parameters conventionally take &str so they accept both a literal and a borrowed String.
let mut s = String::from("hello");
s.push_str(", world"); // String can grow
s.push('!');
fn greet(name: &str) { // accepts &str: works with literals AND &String
println!("Hello, {}!", name);
}
greet("Bob"); // a string literal, is &str
greet(&s); // a borrowed String, coerces to &strHashMap<K, V>: key-value pairs
entry().or_insert() is the idiomatic way to insert a default only if the key is missing, in a single call — a common pattern for counting or building up grouped data.
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
match scores.get("Blue") {
Some(score) => println!("{}", score), // 10
None => println!("no such team"),
}
// count word occurrences: entry().or_insert() pattern
let text = "the quick fox the lazy fox";
let mut counts: HashMap<&str, i32> = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
println!("{:?}", counts.get("fox")); // Some(2)