S

Sajiron

8 min readPublished on Apr 20, 2025

Working with Collections: Vectors, HashMaps, and Strings in Rust

ChatGPT Image Apr 20, 2025, 11_50_51 AM.png

1. Introduction

Rust provides a rich standard library for collections, enabling developers to store, manage, and manipulate data efficiently. Three of the most fundamental collections you'll encounter are:

Vec<T> – growable lists (like Java’s ArrayList)

HashMap<K, V> – key-value stores (like dictionaries or maps)

String – dynamic, growable, UTF-8 encoded text

In this post, we’ll walk through how to use each of them with practical examples and detailed explanations.

2. Vectors (Vec<T>)

What Are Vectors?

Vectors in Rust are dynamic arrays that can grow or shrink in size. They store elements contiguously on the heap, which makes them efficient for indexed access and iteration. They're the go-to data structure for managing ordered lists of items.

Creating Vectors

let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);

let letters = vec!['a', 'b', 'c'];

Vec::new() creates an empty vector.

vec![...] is the macro version — a convenient way to create a vector with initial values.

You can only push values of the same type (Vec is generic over T).

Accessing Elements

println!("{}", numbers[0]);       // Direct index access
println!("{:?}", numbers.get(2)); // Safer access, returns Option<&T>

Using numbers[2] panics if the index is out of bounds.

Using .get(index) returns Some(&value) or None, which is safer and allows you to handle missing values explicitly.

Iterating Over a Vector

for n in &numbers {
println!("{}", n);
}

You can iterate with for item in &vec, which gives references.

Use for item in vec if you want to consume the vector.

For mutable references: for item in &mut vec.

Removing Items

numbers.pop();         // Removes and returns the last item
numbers.remove(0); // Removes element at a specific index

pop() returns an Option<T>, so you can check if it succeeded.

remove(index) will panic if the index is invalid.

Why Use Vectors?

Useful when working with dynamic lists.

Efficient memory layout.

Built-in iteration and functional methods like .map(), .filter() with iter().

3. Strings

What Is a String in Rust?

Rust has two main string types:

&str: a borrowed string slice, usually used for static or borrowed string literals.

String: an owned, growable, heap-allocated string type.

We mostly use String when working with dynamic or modifiable text.

Creating Strings

let mut s = String::from("Hello");
s.push(' ');
s.push_str("Rust!");

String::from converts a string literal to a String.

push(char) adds a single character.

push_str(&str) appends a string slice.

Iterating and Slicing

for c in s.chars() {
println!("{}", c);
}

let slice = &s[0..5]; // Gets "Hello"

s.chars() returns an iterator over Unicode scalar values (characters).

Be cautious when slicing — slicing at invalid UTF-8 boundaries will panic.

Common Methods

let contains = s.contains("Rust");
let replaced = s.replace("Rust", "World");
let trimmed = s.trim();

contains checks if a substring exists.

replace replaces occurrences of a pattern.

trim removes leading and trailing whitespace.

Why Strings Matter?

Strings in Rust are UTF-8 encoded, which means they can store international characters safely.

Strings must be managed carefully due to Rust’s safety guarantees — no nulls, and you must own or borrow properly.

The compiler helps catch common bugs around encoding, length, and indexing.

4. HashMaps

What Is a HashMap?

A HashMap<K, V> is an unordered collection that maps keys to values. It uses a hashing algorithm to provide fast lookup, insert, and delete operations.

Creating a HashMap

use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert("Alice", 90);
scores.insert("Bob", 85);

Keys and values must be of consistent types.

The insert() method overwrites any existing value for the key.

Accessing Values

if let Some(score) = scores.get("Alice") {
println!("Score: {}", score);
}

.get(key) returns an Option<&V>.

Always check for None to avoid panics.

Updating & Overwriting

scores.insert("Alice", 95); // Overwrites Alice’s score

scores.entry("Charlie").or_insert(70); // Inserts only if key doesn't exist

.entry().or_insert() is useful for initializing values if absent.

This is often used for word counts, grouping, or aggregating data.

Iterating Over a HashMap

for (name, score) in &scores {
println!("{}: {}", name, score);
}

Use a reference to avoid moving the map.

HashMap iteration order is not guaranteed (it's unordered).

HashMap Use Cases

Implementing a cache

Counting word frequencies

Mapping configuration values

Grouping and categorizing data

When to Use What?

Collection

Use When...

Vec<T>

You need an ordered, indexable list

String

You're storing or manipulating dynamic text

HashMap

You need key-based lookups or mappings

Think of these collections as building blocks for all kinds of applications — from simple scripts to complex systems.

5. Conclusion

Rust's collection types provide the perfect blend of safety, performance, and flexibility. In this post, you learned how to:

Build and modify growable arrays with Vec<T>

Create and manipulate text with String

Store and retrieve data using HashMap<K, V>

Understanding these will take you far in writing idiomatic, high-performance Rust programs.