Free Handbook · Every example compiled & verified

Collections & Strings

Vec, slices, String vs &str, UTF-8 and why s[0] does not compile, HashMap with the entry API, HashSet, BTreeMap and VecDeque.

0 / 140 lessons🔥 0 day streak
ShareXLinkedIn

Module 05 · what you'll be able to do

  • Create and grow a Vec, and choose between v[i] (panics) and v.get(i) (returns an Option)
  • Loop over a collection by shared reference, mutable reference or by value, and predict which one moves the Vec
  • Explain the difference between String and &str, build strings with push_str, format! and +, and say who owns the result
  • Explain why s[0] does not compile, and walk a string by chars or bytes instead
  • Count and group with HashMap and the entry API, and pick HashSet, BTreeMap or VecDeque when they fit better
01

Vec: a growable list

A Vec<T> ("vector") is Rust's growable list: every element has the same type T, they sit next to each other on the heap, and the Vec can grow or shrink. It is the collection you will use most, the way Python uses list and Java uses ArrayList. Create one with Vec::new() or the vec! macro.

rustmain.rs
fn main() {
    let mut langs: Vec<String> = Vec::new();   // empty; the type is written out
    langs.push(String::from("rust"));
    langs.push(String::from("go"));
    langs.push(String::from("python"));

    let primes = vec![2, 3, 5, 7];              // vec! infers Vec<i32>
    let zeros = vec![0; 4];                     // four zeros

    println!("{:?} len={}", langs, langs.len());
    println!("{:?} {:?}", primes, zeros);
    println!("first={} last={:?}", primes[0], primes.last());

    let popped = langs.pop();                   // Option<String>
    println!("popped {:?}, left {:?}", popped, langs);
    langs.insert(0, String::from("c"));         // shifts everything right
    langs.remove(1);                            // shifts everything left
    println!("{:?} contains go? {}", langs, langs.contains(&String::from("go")));
}
Outputcompiled & run with real Rust
["rust", "go", "python"] len=3
[2, 3, 5, 7] [0, 0, 0, 0]
first=2 last=Some(7)
popped Some("python"), left ["rust", "go"]
["c", "go"] contains go? true
Your turn

Call langs.pop() three more times and print each result. What does the third one return, and why?

v[i] or v.get(i)?

There are two ways to read element i. v[i] gives you the element directly and panics (crashes the program with a message) if i is out of range. v.get(i) never panics: it returns Option<&T>, Some(&element) or None. Use indexing when an out-of-range index would be a bug in your program; use get when the index comes from outside (user input, a file, another service).

rustmain.rs
fn main() {
    let scores = vec![90, 72, 85];
    for i in [1, 5] {
        match scores.get(i) {
            Some(s) => println!("scores[{i}] = {s}"),
            None => println!("no score at {i}"),
        }
    }
    println!("{}", scores.get(9).copied().unwrap_or(0));
}
Outputcompiled & run with real Rust
scores[1] = 72
no score at 5
0
Error you will hit

Runtime panic: index out of bounds

rust
fn main() {
    let scores = vec![90, 72, 85];
    let i = 3;
    println!("{}", scores[i]);
}
thread 'main' (13230805) panicked at main.rs:4:26:
index out of bounds: the len is 3 but the index is 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Why the compiler said that

The Vec has three elements, at indexes 0, 1 and 2. Rust checks every index at run time; instead of reading whatever memory sits after the Vec (what C would do), it stops the program. This is a panic, not a compile error: the compiler cannot know the value of i in general.

The fix

Check the length first, or use scores.get(i) and handle None. The panic message gives the file, line and column, and both the length and the index you asked for, which is usually enough to spot an off-by-one.

rust
fn main() {
    let scores = vec![90, 72, 85];
    let i = 3;
    match scores.get(i) {
        Some(s) => println!("{s}"),
        None => println!("no score at index {i} (len {})", scores.len()),
    }
}
Capacity: why push is fast
A Vec keeps a capacity larger than its length. When a push would overflow it, the Vec allocates a bigger block (roughly double), copies the elements over and frees the old block. That makes push cheap on average. If you know the final size, Vec::with_capacity(n) allocates once up front.
02

Looping over a Vec: &v, &mut v and v

A for loop over a Vec can take the elements in three ways, and the choice is the ownership story from Module 03 again:

You writeEach item isAfter the loop
for x in &v&T — a shared borrowv is unchanged and usable
for x in &mut v&mut T — change it with *xv holds the changed values
for x in vT — each element moved outv is gone (moved into the loop)
rustmain.rs
fn main() {
    let mut prices = vec![100, 250, 40];

    for p in &prices {                 // read
        print!("{p} ");
    }
    println!();

    for p in &mut prices {             // change in place
        *p = *p * 110 / 100;           // * reaches the value behind the reference
    }
    println!("{:?}", prices);

    let names = vec![String::from("ada"), String::from("linus")];
    for name in names {                // take ownership of each String
        let shout = name.to_uppercase();
        println!("{shout}");
    }
    // names cannot be used here: the loop consumed it
}
Outputcompiled & run with real Rust
100 250 40 
[110, 275, 44]
ADA
LINUS
VisualizeDoubling in place with &mutStep 1 / 6
fn main() {
let mut v = vec![1, 2, 3];
for x in &mut v {
*x *= 2;
}
println!("{:?}", v);
}
Line 2

A Vec of three i32 on the heap.

Variables now
v[1, 2, 3]
All 6 steps as a table
StepLineWhat happenedVariables now
12A Vec of three i32 on the heap.v = [1, 2, 3]
23&mut v borrows the whole Vec mutably for the loop. The first item is a &mut i32 pointing at element 0.x = &mut v[0]
34*x is the element itself; it becomes 2.v = [2, 2, 3]
44Next item: x points at element 1, which becomes 4.x = &mut v[1] v = [2, 4, 3]
54Last item: element 2 becomes 6. The loop ends and the mutable borrow ends with it.x = &mut v[2] v = [2, 4, 6]
66With the borrow over, v can be read again.
Error you will hit

E0382: using a Vec after looping over it by value

rust
fn main() {
    let names = vec![String::from("ada"), String::from("linus")];
    for name in names {
        println!("{name}");
    }
    println!("{} names", names.len());
}
error[E0382]: borrow of moved value: `names`
   --> main.rs:6:26
    |
  2 |     let names = vec![String::from("ada"), String::from("linus")];
    |         ----- move occurs because `names` has type `Vec<String>`, which does not implement the `Copy` trait
  3 |     for name in names {
    |                 ----- `names` moved due to this implicit call to `.into_iter()`
...
  6 |     println!("{} names", names.len());
    |                          ^^^^^ value borrowed here after move
    |
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
    |
  3 |     for name in &names {
    |                 +
Why the compiler said that

for name in names calls into_iter() on the Vec, which takes ownership of it and hands out each String by value. After the loop the Vec no longer exists.

The fix

Loop over &names if you only need to read, exactly as the help line suggests. Loop by value only when you really want to consume the elements (to move them somewhere else).

rust
fn main() {
    let names = vec![String::from("ada"), String::from("linus")];
    for name in &names {
        println!("{name}");
    }
    println!("{} names", names.len());
}
Error you will hit

E0502: pushing to a Vec while looping over it

rust
fn main() {
    let mut v = vec![1, 2, 3];
    for x in &v {
        if *x == 2 {
            v.push(4);
        }
    }
    println!("{:?}", v);
}
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> main.rs:5:13
  |
3 |     for x in &v {
  |              --
  |              |
  |              immutable borrow occurs here
  |              immutable borrow later used here
4 |         if *x == 2 {
5 |             v.push(4);
  |             ^^^^^^^^^ mutable borrow occurs here
Why the compiler said that

The loop holds a shared borrow of v for its whole length, and push needs a mutable one. This is not pedantry: push may reallocate the Vec, which would leave the loop reading freed memory. In C++ this is the classic iterator-invalidation bug; in Rust it does not compile.

The fix

Collect what you want to add in a separate Vec and extend after the loop, or loop over indexes (for i in 0..v.len()) when you really need to change the Vec as you go.

rust
fn main() {
    let mut v = vec![1, 2, 3];
    let mut extra = Vec::new();
    for x in &v {
        if *x == 2 {
            extra.push(4);
        }
    }
    v.extend(extra);
    println!("{:?}", v);
}
03

Slices: &[T], a view into a Vec

A slice &[T] is a borrowed view of a run of elements: a pointer plus a length, owning nothing. &v[1..3] is elements 1 and 2. A function that only reads a list should take &[T], not &Vec<T>: it then accepts a Vec, an array, or part of either.

rustmain.rs
fn average(xs: &[f64]) -> f64 {
    if xs.is_empty() {
        return 0.0;
    }
    xs.iter().sum::<f64>() / xs.len() as f64
}

fn main() {
    let temps = vec![21.0, 23.5, 19.0, 25.5, 22.0];
    let week = [1.0, 2.0, 3.0];                    // an array works too

    println!("all   {:.2}", average(&temps));
    println!("first {:.2}", average(&temps[..2]));  // elements 0 and 1
    println!("last3 {:.2}", average(&temps[2..]));
    println!("array {:.2}", average(&week));
    println!("empty {:.2}", average(&[]));

    let mut sorted = temps.clone();
    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); // f64 has no total order
    println!("{:?}", sorted);
    let (low, high) = sorted.split_at(2);
    println!("low {:?} high {:?}", low, high);
}
Outputcompiled & run with real Rust
all   22.20
first 22.25
last3 22.17
array 2.00
empty 0.00
[19.0, 21.0, 22.0, 23.5, 25.5]
low [19.0, 21.0] high [22.0, 23.5, 25.5]

&temps[2..] means "from index 2 to the end"; &temps[..2] means "from the start up to, not including, 2". An out-of-range slice panics just like an out-of-range index.

Slice methodDoes
len(), is_empty()Size checks
first(), last(), get(i)Safe reads that return Option
contains(&x), iter().position(...)Search (linear)
sort(), sort_by_key(...), reverse()Reorder in place (needs &mut)
binary_search(&x)Fast search on sorted data
split_at(i), chunks(n), windows(n)Cut into sub-slices
join(", ")Glue a slice of strings into one String
04

String vs &str

Rust has two main string types, and the split is the same as Vec<T> versus &[T]:

String

  • Owned, growable text on the heap
  • You can push to it and change it (if mut)
  • Freed when its owner goes out of scope
  • Made with String::from, .to_string(), format!

&str

  • A borrowed view of some UTF-8 text
  • Read only; owns nothing
  • Points into a String, or into the program binary for literals
  • String literals like "hi" are &'static str

The rule of thumb: take &str in parameters, return String when you build new text. A &String automatically turns into a &str when passed to such a function (deref coercion), so callers can pass either.

rustmain.rs
fn greet(name: &str) -> String {
    format!("Hello, {name}!")               // format! builds a new String
}

fn main() {
    let literal: &str = "world";            // baked into the program
    let owned: String = String::from("Ada");

    println!("{}", greet(literal));
    println!("{}", greet(&owned));          // &String coerces to &str

    let mut log = String::new();
    log.push_str("GET /");                  // append a &str
    log.push(' ');                          // append one char
    log.push_str("200");
    log += " OK";                           // += also takes a &str
    println!("{log} ({} bytes)", log.len());

    let csv = ["a", "b", "c"].join(",");
    let id = 42.to_string();
    println!("{csv} {id} {}", "  padded  ".trim());
}
Outputcompiled & run with real Rust
Hello, world!
Hello, Ada!
GET / 200 OK (12 bytes)
a,b,c 42 padded

Concatenation and ownership

s1 + &s2 works, but look at what it does: + takes the left String by value, appends the right side to its buffer, and returns it. So s1 is moved and cannot be used again, while s2 is only borrowed. For anything more than two pieces, format! is clearer and moves nothing.

rustmain.rs
fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = s1 + "-" + &s2;        // s1 moved into s3; s2 borrowed
    println!("{s3}");
    println!("{s2} still usable");

    let a = String::from("x");
    let b = String::from("y");
    let c = format!("{a}-{b}-{a}");  // borrows only
    println!("{c} {a} {b}");
}
Outputcompiled & run with real Rust
tic-tac
tac still usable
x-y-x x y
Error you will hit

E0369: adding two &str

rust
fn main() {
    let first = "Ada";
    let last = "Lovelace";
    let full = first + " " + last;
    println!("{full}");
}
error[E0369]: cannot add `&str` to `&str`
 --> main.rs:4:22
  |
4 |     let full = first + " " + last;
  |                ----- ^ --- &str
  |                |     |
  |                |     `+` cannot be used to concatenate two `&str` strings
  |                &str
  |
  = note: string concatenation requires an owned `String` on the left
help: create an owned `String` from a string reference
  |
4 |     let full = first.to_owned() + " " + last;
  |                     +++++++++++
Why the compiler said that

+ on strings is defined only as String + &str, because it needs an owned buffer on the left to append into. Two &str are both borrowed views; there is nowhere to put the result.

The fix

Start from an owned String (first.to_owned() + ..., as the help suggests), or, cleaner, use format!("{first} {last}").

rust
fn main() {
    let first = "Ada";
    let last = "Lovelace";
    let full = format!("{first} {last}");
    println!("{full}");
}
05

UTF-8: why s[0] does not compile

Every Rust string is UTF-8. ASCII letters take 1 byte, but é takes 2, most other scripts take 2 or 3, and an emoji takes 4. len() counts bytes, not characters. So "the character at position 0" is not a cheap operation, and for many strings "byte 1" is the middle of a character. Rust refuses to guess what you meant.

Error you will hit

E0277: indexing a String with an integer

rust
fn main() {
    let s = String::from("hello");
    let c = s[0];
    println!("{c}");
}
error[E0277]: the type `str` cannot be indexed by `{integer}`
   --> main.rs:3:15
    |
  3 |     let c = s[0];
    |               ^ string indices are ranges of `usize`
    |
    = help: the trait `SliceIndex<str>` is not implemented for `{integer}`
    = note: you can use `.chars().nth()` or `.bytes().nth()`
            for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>
Why the compiler said that

Indexing with an integer would have to return either a byte (not what most people want) or a character (which needs a scan from the start, hiding an O(n) cost behind []). Rust does neither and makes you say which one you mean.

The fix

Ask for what you mean: s.chars().nth(0) for a character (an Option<char>), s.as_bytes()[0] for a byte, or &s[0..1] for a &str slice by byte range (which panics if the range cuts a character in half).

rust
fn main() {
    let s = String::from("hello");
    let c = s.chars().nth(0).unwrap();
    println!("{c}");
}
rustmain.rs
fn main() {
    let word = "café";
    println!("bytes: {}  chars: {}", word.len(), word.chars().count());

    for c in word.chars() {
        print!("[{c}]");
    }
    println!();

    for b in word.bytes() {
        print!("{b} ");
    }
    println!();

    for (i, c) in word.char_indices() {     // byte offset of each char
        print!("{i}:{c} ");
    }
    println!();

    println!("{}", &word[0..3]);             // bytes 0..3 = "caf", safe
    let reversed: String = word.chars().rev().collect();
    println!("{reversed} {}", word.to_uppercase());
}
Outputcompiled & run with real Rust
bytes: 5  chars: 4
[c][a][f][é]
99 97 102 195 169 
0:c 1:a 2:f 3:é 
caf
éfac CAFÉ

é is 2 bytes (195 169), so "café" is 4 characters but 5 bytes. &word[0..4] would panic: byte 4 is inside the é.

Error you will hit

Runtime panic: slicing inside a character

rust
fn main() {
    let word = "café";
    let head = &word[0..4];
    println!("{head}");
}
thread 'main' (13232275) panicked at main.rs:3:21:
end byte index 4 is not a char boundary; it is inside 'é' (bytes 3..5 of string)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Why the compiler said that

Byte range slicing is allowed, but both ends must fall on character boundaries. Byte 4 is the second byte of é, so the slice would not be valid UTF-8, and Rust panics rather than hand you a broken string.

The fix

Slice at offsets you got from the string itself (char_indices(), find()), or work in characters: word.chars().take(4).collect::<String>().

rust
fn main() {
    let word = "café";
    let head: String = word.chars().take(4).collect();
    println!("{head}");
}
You wantWriteGives
Size in bytess.len()usize, O(1)
Number of characterss.chars().count()usize, O(n)
The n-th characters.chars().nth(n)Option<char>
Raw bytess.as_bytes() or s.bytes()&[u8] / iterator of u8
Find a substrings.find("x")Option<usize> byte offset
Split into wordss.split_whitespace()iterator of &str
Owned copys.to_string() / to_owned()String
06

HashMap and the entry API

A HashMap<K, V> maps keys to values with O(1) average lookup. It lives in std::collections, so it needs a use line. Two things surprise newcomers: get returns an Option<&V> (the key might be missing), and iteration order is random — it changes between runs on purpose, so never print a HashMap and expect a fixed order. Sort the keys, or use a BTreeMap (next lesson).

rustmain.rs
use std::collections::HashMap;

fn main() {
    let mut stock: HashMap<String, u32> = HashMap::new();
    stock.insert(String::from("apple"), 5);
    stock.insert(String::from("pear"), 2);
    stock.insert(String::from("apple"), 7);      // same key: value replaced

    println!("apples: {:?}", stock.get("apple"));
    println!("kiwis:  {:?}", stock.get("kiwi"));
    println!("pears or 0: {}", stock.get("pear").copied().unwrap_or(0));
    println!("has pear? {} len={}", stock.contains_key("pear"), stock.len());

    if let Some(n) = stock.get_mut("pear") {
        *n += 10;
    }
    stock.remove("apple");

    let mut keys: Vec<_> = stock.keys().collect();
    keys.sort();                                  // fixed order for printing
    for k in keys {
        println!("{k} -> {}", stock[k]);
    }
}
Outputcompiled & run with real Rust
apples: Some(7)
kiwis:  None
pears or 0: 2
has pear? true len=2
pear -> 12

stock[k] works like Vec indexing: it panics if the key is missing. Use it only when you know the key is there, as here.

The entry API: insert-or-update in one lookup

"If the key is there, update it; otherwise insert a starting value" is the most common map operation. map.entry(key) looks the key up once and returns an Entry; .or_insert(0) inserts the default if needed and returns a &mut V you can change. Counting words is the classic example.

rustmain.rs
use std::collections::HashMap;

fn main() {
    let text = "the cat sat on the mat the end";
    let mut counts: HashMap<&str, usize> = HashMap::new();

    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }

    // Sort by count (highest first), then alphabetically for ties
    let mut pairs: Vec<(&str, usize)> = counts.into_iter().collect();
    pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
    for (word, n) in &pairs {
        println!("{word:<4} {n}");
    }

    // Group words by their first letter
    let mut groups: HashMap<char, Vec<&str>> = HashMap::new();
    for word in ["apple", "avocado", "banana", "blueberry", "cherry"] {
        let first = word.chars().next().unwrap();
        groups.entry(first).or_default().push(word);
    }
    let mut letters: Vec<_> = groups.keys().copied().collect();
    letters.sort();
    for c in letters {
        println!("{c}: {:?}", groups[&c]);
    }
}
Outputcompiled & run with real Rust
the  3
cat  1
end  1
mat  1
on   1
sat  1
a: ["apple", "avocado"]
b: ["banana", "blueberry"]
c: ["cherry"]
Your turn

Make the count case-insensitive: "The" and "the" should be one word. Hint: build the key with word.to_lowercase() and change the map to HashMap<String, usize>.

Visualizeentry().or_insert() countingStep 1 / 5
let mut counts = HashMap::new();
for word in "a b a".split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
Line 1

An empty map.

Variables now
counts{}
All 5 steps as a table
StepLineWhat happenedVariables now
11An empty map.counts = {}
22First word.word = "a"
33"a" is missing, so or_insert(0) inserts 0 and returns &mut 0; += 1 makes it 1.counts = {"a": 1}
43"b" is missing: inserted as 0, then incremented.word = "b" counts = {"a": 1, "b": 1}
53"a" exists: or_insert ignores its argument and returns a reference to the existing 1, which becomes 2.word = "a" counts = {"a": 2, "b": 1}
Maps take ownership of String keys
Inserting a String key or value moves it into the map. If you need the string afterwards, insert a clone, or keep the map's own copy and borrow it back with get. A map of &str keys (as above) borrows instead, which means the map cannot outlive the text it points into.
07

HashSet, BTreeMap and VecDeque

Three more collections from std::collections cover most of what is left. A HashSet is a HashMap with only keys: fast membership tests and de-duplication. A BTreeMap is a map kept sorted by key, so iteration order is always ascending (O(log n) per operation instead of O(1)). A VecDeque is a ring buffer with cheap push and pop at both ends, the right type for a queue.

rustmain.rs
use std::collections::{BTreeMap, HashSet, VecDeque};

fn main() {
    // HashSet: unique values, fast "have I seen this?"
    let visits = ["home", "docs", "home", "blog", "docs", "home"];
    let unique: HashSet<&str> = visits.iter().copied().collect();
    println!("{} unique pages, seen blog? {}", unique.len(), unique.contains("blog"));

    let a: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();
    let b: HashSet<i32> = [3, 4, 5].into_iter().collect();
    let mut both: Vec<_> = a.intersection(&b).copied().collect();
    both.sort();
    println!("in both: {:?}", both);

    // BTreeMap: always iterates in key order
    let mut grades = BTreeMap::new();
    grades.insert("maya", 88);
    grades.insert("ali", 93);
    grades.insert("zoe", 71);
    println!("{:?}", grades);
    println!("first: {:?}", grades.first_key_value());

    // VecDeque: a first-in, first-out queue
    let mut queue = VecDeque::new();
    queue.push_back("job1");
    queue.push_back("job2");
    queue.push_front("urgent");
    while let Some(job) = queue.pop_front() {
        print!("{job} ");
    }
    println!();
}
Outputcompiled & run with real Rust
3 unique pages, seen blog? true
in both: [3, 4]
{"ali": 93, "maya": 88, "zoe": 71}
first: Some(("ali", 93))
urgent job1 job2

Printing a whole BTreeMap with {:?} is safe: its order is defined. Doing the same with a HashMap gives a different order on different runs.

Module 13 builds on this table with BinaryHeap and hand-built structures.
CollectionUse whenKey cost
Vec<T>An ordered list; the default choicepush/pop at end O(1), index O(1), search O(n)
VecDeque<T>A queue: add at one end, remove at the otherpush/pop at both ends O(1)
HashMap<K, V>Look up values by key, order does not matterinsert/get O(1) average
BTreeMap<K, V>Look up by key AND need sorted order or rangesinsert/get O(log n)
HashSet<T>Unique values, membership testsinsert/contains O(1) average
BTreeSet<T>Unique values in sorted orderO(log n)
In real codebases
Most Rust services use Vec and HashMap for almost everything, and reach for BTreeMap when output has to be stable: config files written back to disk, API responses compared in tests, or anything a human diffs. Deterministic order is worth the O(log n).
Vec<T>
A growable list of values of one type, stored contiguously on the heap.
Slice &[T]
A borrowed view of a run of elements: pointer plus length. Works for Vecs and arrays.
Panic
A controlled crash with a message, e.g. on an out-of-bounds index. Not a compile error.
String
Owned, growable UTF-8 text on the heap.
&str
A borrowed view of UTF-8 text. String literals are &'static str.
UTF-8
The encoding of every Rust string: 1 to 4 bytes per character, which is why strings are not indexed by integer.
HashMap<K, V>
Key-value map with O(1) average lookups and no defined iteration order.
Entry API
map.entry(k).or_insert(v): find-or-insert in one lookup, returning a &mut V.
BTreeMap<K, V>
A map kept sorted by key; iteration is always in key order.
VecDeque<T>
A double-ended queue (ring buffer): O(1) push and pop at both ends.
Quick check

let s = String::from("héllo"); What does s.len() return?

Quick check

You loop with for name in names where names is a Vec, then call names.len(). What happens?

Frequently asked questions

What is the difference between String and &str in Rust?
String owns its text on the heap and can grow; &str is a borrowed, read-only view of text owned by something else (a String, or the program binary for literals). Functions usually take &str and return String.
Why can't I index a string with s[0] in Rust?
Rust strings are UTF-8, where one character can be 1 to 4 bytes. An integer index would have to either return a raw byte or secretly scan the string, so Rust refuses (E0277). Use s.chars().nth(0) for a character or s.as_bytes()[0] for a byte.
Why does my HashMap print in a different order every time?
Rust's HashMap uses a randomly seeded hasher to resist denial-of-service attacks, so iteration order changes between runs. Sort the keys before printing, or use BTreeMap, which always iterates in key order.

Finish the Rust handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.