8. Common Collections

Learning objectives

  • Identify the 3 most common collection types in the Rust standard library
  • Know where collections are stored in memory and why it’s important
  • Be able to access elements of a collection and iterate over all elements
  • Know where to find information about Rust’s collection types and when to use them

Intro

What is a ‘collection’?

Collections are data structures for storing multiple values.

What about arrays and tuples?

  • Yes, arrays and tuples can hold multiple values
  • But, they are stored on the stack while collections are stored on the heap
    • array, tuple: fixed size, known at compile time
    • collections: variable size, not known at compile time

Common collections

  • vector: collection of numbers
  • string: collection of characters
  • hash map: collection of key-value pairs

Vectors

Creating vectors

You can create a vector with new and add values with .push

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

v.push(1);
v.push(2);
v.push(3);

Or you can create it directly with vec!

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

Access elements

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

[]

let first_element: &i32 = &v[0];

get

let first_element: Option<&i32> = v.get(0);

Access elements - ownership & borrowing

This program will not compile

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

let first = &v[0]; // immutable borrow

v.push(6); // mutable borrow

println!("The first element is: {first}"); // immutable borrow used

Iteration

Immutable vector, immutable references

let v = vec![1, 2, 3];
for i in &v {
  println!("{i}");
}
// v = [1, 2, 3]
// output: 1, 2, 3

Mutable vector, mutable references

let mut v = vec![1, 2, 3];
for i in &mut v {
  *i += 1;
}
// v = [2, 3, 4]

for i in &v {
    println!("{i}");
}

// output: 2, 3, 4

Vectors with multiple types

enum

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

let row = vec![
  SpreadsheetCell::Int(3),
  SpreadsheetCell::Float(10.12),
];

struct

struct Ex {
    number: i32,
    string: String,
}

let v = vec![
    Ex {number: 1, string: String::from("string")},
    Ex {number: 1, string: String::from("2nd string")},
];

Strings

What are Strings?

  • Collection of bytes
  • In Rust standard library
  • Built around vectors
  • UTF-8

String vs str

String

  • standard library
  • dynamic size
  • vector

str

  • built into the language
  • fixed size
  • slice of the vector

Creating Strings

You can create a String with new and add values with .push_str

let mut s = String::new();
s.push_str("this is");
s.push_str("a string");

Or you can create it directly

let s1 = String::from("this is a string");
let s2 = "this is a string".to_string();

Combining Strings

+

let s1 = String::from("this is ");
let s2 = String::from("a string");

let s3 = s1 + &s2;

format!

let s1 = String::from("this is");
let s2 = String:: from("a string")

let s3 = format!("{s1} ... {s2}!") // "this is ... a string!"

Access elements

let s = String::from("this is a string")
let first_char = s[0];

Computer says no GIF

Internal representation of Strings

  • Wrapper around Vec<u8>
  • Vector of bytes
  • UTF-8
let hello = String::from("hello");
let konichiwa = String::from("こんにちは");

println!("Hello bytes: {}", hello.len()); // 5
println!("Konichiwa bytes:{}", konichiwa.len()); // 15

String slices

let hello = String::from("hello");
println!("Hello first: {}", &hello[0..1]); // h
let konichiwa = String::from("こんにちは");
println!("Konichiwa first: {}", &konichiwa[0..1]); //error

Iteration

.chars()

let hello = String::from("hello");
let konichiwa = String::from("こんにちは");

for c in hello.chars() {
    println!("{c}");
} // h, e, l, l, o

for c in konichiwa.chars() {
    println!("{c}");
} // こ, ん, に, ち, は

.bytes()

let hello = String::from("hello");
let konichiwa = String::from("こんにちは");

for b in hello.bytes() {
    println!("{b}");
} // 104, 101, ...

for b in konichiwa.bytes() {
    println!("{b}");
} // 227, 129, ...

Hash maps

Creating hash maps

You can create a hash map with new and add values with .insert

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Red"), 10);
scores.insert(String::from("Blue"), 20);

Access elements

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Red"), 10);
scores.insert(String::from("Blue"), 20);

let blue_team = String::from("Blue");
let blue_score = scores.get(&blue_team).copied().unwrap_or(0); // 20
  • get returns an Option<&V>
  • copied returns an Option<i32> instead of Option<&i32>
  • unwrap_or returns 0 if there is no entry for "Blue"

Iteration

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Red"), 10);
scores.insert(String::from("Blue"), 20);

for (key, value) in &scores {
  println!("{key}: {value}");
} // Red: 10, Blue: 20

Updating - overwrite

use std::collections::HashMap;

let mut scores = HashMap::new()

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

Updating - add if not present

use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Red")).or_insert(50); // added
scores.entry(String::from("Blue")).or_insert(50); // skipped

Updating - modify existing value

use std::collections::HashMap;

let text = "hello world wonderful world";

let mut map = HashMap::new();

for word in text.split_whitespace() {
  let count = map.entry(word).or_insert(0);
  *count += 1;
}
println!("{map:?}"); // {"world": 2, "hello": 1, "wonderful": 1}

Resources

Collections Documentation