Rust Standard Library Idioms: Small Tricks You'll Use Every Day
Every kitchen has a drawer of small tools: a peeler, a bottle opener, a garlic press. You could do every job with one big knife, but the small tools make the job quicker and safer. Rust’s standard library has a drawer like that too.
This page is a tour of that drawer. Each trick is short: a real-life picture, a runnable example, and a “use it when…” line so you know when to reach for it. You don’t need to learn them all at once. Skim the page now, and come back when you see a pattern in your own code that feels clumsy.
Moving a value out of &mut: mem::take, replace, swap
Section titled “Moving a value out of &mut: mem::take, replace, swap”Picture a rubbish bin with a bag in it. You may take the full bag out, but only if you put a fresh bag in right away. The bin is never left without a bag.
Rust has the same rule. If you only have &mut self, you can’t simply move a field out, because that would leave a hole in the struct. The compiler stops you with E0507. The std::mem functions solve this by always leaving something behind:
mem::take(&mut x)takes the value and leaves the type’s default (an emptyVec, an emptyString,0,None).mem::replace(&mut x, new)putsnewin and gives you the old value.Option::take()takes what’s inside anOptionand leavesNone.
use std::mem;
struct Inbox { unread: Vec<String>, status: String, draft: Option<String>,}
impl Inbox { // Hand over every unread message and leave an empty Vec behind. fn read_all(&mut self) -> Vec<String> { mem::take(&mut self.unread) }
// Put a new status in and get the old one back. fn set_status(&mut self, new: &str) -> String { mem::replace(&mut self.status, new.to_string()) }
// Take the draft out (if there is one) and leave None. fn send_draft(&mut self) -> Option<String> { self.draft.take() }}
fn main() { let mut inbox = Inbox { unread: vec!["Lunch?".to_string(), "Invoice #42".to_string()], status: "online".to_string(), draft: Some("See you at 6".to_string()), };
let messages = inbox.read_all(); println!("read {:?}, still unread: {:?}", messages, inbox.unread);
let old = inbox.set_status("in a meeting"); println!("status was '{old}', now '{}'", inbox.status);
println!("sent: {:?}", inbox.send_draft()); println!("sent again: {:?}", inbox.send_draft());}Output:
read ["Lunch?", "Invoice #42"], still unread: []status was 'online', now 'in a meeting'sent: Some("See you at 6")sent again: NoneNo clone() anywhere: the messages and the old status are moved out, not copied. That’s why these functions are both safe and cheap.
Two relatives finish the set. mem::swap(&mut a, &mut b) trades two values, like swapping two plates on a table. Option::replace(new) puts a new value into an Option and gives back the old one.
use std::mem;
fn main() { // Fill one list while the other one is being worked on. let mut filling = vec!["order 1", "order 2"]; let mut processing: Vec<&str> = Vec::new();
mem::swap(&mut filling, &mut processing); println!("processing {processing:?}, filling {filling:?}");
// Option::replace: put a new value in, get the old one back. let mut now_playing = Some("Song A"); let previous = now_playing.replace("Song B"); println!("was {previous:?}, now {now_playing:?}");}Output:
processing ["order 1", "order 2"], filling []was Some("Song A"), now Some("Song B")Use it when you only have &mut access but need to own the value inside.
Slices: windows, chunks and friends
Section titled “Slices: windows, chunks and friends”Think of windows(2) as a magnifying glass sliding along a ruler: it shows two marks at a time, then moves one step. chunks(3) is like packing eggs into boxes of three: the last box may be only partly full. chunks_exact(3) only gives you full boxes and keeps the leftover eggs aside in .remainder().
These methods work on any slice, so they work on arrays and vectors too.
fn main() { let temps = [12, 15, 14, 19, 21];
// windows(2): every pair of neighbours, overlapping for pair in temps.windows(2) { println!("{} -> {}: change {}", pair[0], pair[1], pair[1] - pair[0]); }
// chunks(3): batches of 3, the last one may be smaller let emails = ["a@x.com", "b@x.com", "c@x.com", "d@x.com", "e@x.com"]; for batch in emails.chunks(3) { println!("sending to {batch:?}"); }
// chunks_exact(2): only full batches; the rest waits in remainder() let seats = [1, 2, 3, 4, 5]; let pairs = seats.chunks_exact(2); let leftover = pairs.remainder(); for pair in pairs { println!("pair {pair:?}"); } println!("leftover: {leftover:?}");}Output:
12 -> 15: change 315 -> 14: change -114 -> 19: change 519 -> 21: change 2sending to ["a@x.com", "b@x.com", "c@x.com"]sending to ["d@x.com", "e@x.com"]pair [1, 2]pair [3, 4]leftover: [5]A few more slice helpers answer common questions without a loop. split_first() gives you the first item and the rest, like a teacher reading the first name on a list and handing the rest of the list to a helper. split_last() does the same from the end.
fn main() { let command = ["git", "commit", "-m", "fix typo"];
// split_first: the first item, and a slice of the rest if let Some((program, args)) = command.split_first() { println!("program: {program}, args: {args:?}"); }
// split_last: the last item, and everything before it if let Some((message, _rest)) = command.split_last() { println!("message: {message}"); }
println!("is git? {}", command.starts_with(&["git"])); println!("has -m? {}", command.contains(&"-m"));
// iter().rev(): walk backwards without changing the slice let history = ["open", "edit", "save"]; let undo_order: Vec<_> = history.iter().rev().collect(); println!("undo order: {undo_order:?}");}Output:
program: git, args: ["commit", "-m", "fix typo"]message: fix typois git? truehas -m? trueundo order: ["save", "edit", "open"]split_first and split_last return an Option, because an empty slice has no first item. That’s why we use if let.
Use it when you need neighbours (windows), batches (chunks), or “the head and the rest” (split_first).
Iterator helpers you’ll use all the time
Section titled “Iterator helpers you’ll use all the time”The Iterators page covers map, filter, sum and collect. Here are the next tools in the box.
peekable: look before you take
Section titled “peekable: look before you take”Imagine dealing cards. Sometimes you want to look at the next card before you decide to draw it. .peekable() gives an iterator that can do that: .peek() shows the next item without taking it, and .next_if(...) takes it only if it passes a test. This is exactly what small parsers need.
fn main() { let input = "12+345-6"; let mut chars = input.chars().peekable(); let mut tokens: Vec<String> = Vec::new();
while let Some(c) = chars.next() { if c.is_ascii_digit() { let mut number = c.to_string(); // Take the next char only while it is a digit too. while let Some(d) = chars.next_if(|next| next.is_ascii_digit()) { number.push(d); } tokens.push(number); } else { tokens.push(c.to_string()); } }
println!("{tokens:?}");}Output:
["12", "+", "345", "-", "6"]Without peekable, you would read the + while looking for more digits, and then it would be gone.
Use it when the next item decides what you do with the current one.
Walking lists: zip, enumerate, step_by, take_while, skip_while, chain
Section titled “Walking lists: zip, enumerate, step_by, take_while, skip_while, chain”| Method | Think of it as | Use it when… |
|---|---|---|
zip |
a zipper joining two sides tooth by tooth | you have two lists that belong together |
enumerate |
numbering the lines of a list | you need the index as well as the item |
step_by(n) |
climbing stairs two (or n) at a time | you want every n-th item |
take_while |
eating sweets until you hit one you don’t like | you want the start of a list, up to the first “no” |
skip_while |
skipping adverts until the show starts | you want the rest, from the first “no” on |
chain |
coupling two train carriages | you want one list after another |
fn main() { let names = ["Ana", "Ben", "Chen"]; let scores = [88, 92, 75];
// zip pairs two lists up; enumerate adds a counter starting at 0 for (i, (name, score)) in names.iter().zip(scores.iter()).enumerate() { println!("{}. {name}: {score}", i + 1); }
// step_by: every 15th minute of the hour let quarters: Vec<u32> = (0..60).step_by(15).collect(); println!("quarter hours: {quarters:?}");
// into_iter() on an array gives the numbers themselves let queue = [3, 5, 8, 12, 4]; let small: Vec<i32> = queue.into_iter().take_while(|n| *n < 10).collect(); let rest: Vec<i32> = queue.into_iter().skip_while(|n| *n < 10).collect(); println!("before the first big one: {small:?}, from there on: {rest:?}");
// chain: one list after the other let morning = ["email", "standup"]; let afternoon = ["review", "deploy"]; let day: Vec<_> = morning.iter().chain(afternoon.iter()).collect(); println!("{day:?}");}Output:
1. Ana: 882. Ben: 923. Chen: 75quarter hours: [0, 15, 30, 45]before the first big one: [3, 5, 8], from there on: [12, 4]["email", "standup", "review", "deploy"]Notice that take_while stops at 12 for good. The 4 after it is small, but it never gets checked. That’s the difference from filter, which looks at every item.
Answering questions: fold, position, find_map, partition, flat_map
Section titled “Answering questions: fold, position, find_map, partition, flat_map”| Method | Think of it as | Use it when… |
|---|---|---|
sum |
a calculator’s plus key | you just want the total |
fold |
a snowball rolling downhill, growing at each step | you build one result your own way |
position |
“which seat number is Ben in?” | you need the index of the first match |
find_map |
trying keys until one opens the door | you try something on each item and keep the first success |
partition |
sorting laundry into two baskets | you split one list into two by a yes/no question |
flat_map |
emptying several bags onto one table | each item turns into several items |
fn main() { let orders = [12, 40, 7, 55, 30];
// sum: just add them up let total: i32 = orders.iter().sum();
// fold: add them up your way (here: each order also pays a 2 euro fee) let with_fees = orders.iter().fold(0, |acc, price| acc + price + 2); println!("total {total}, with fees {with_fees}");
// position: the index of the first match println!("first order over 50 is at index {:?}", orders.iter().position(|p| *p > 50));
// partition: two Vecs, one for "yes" and one for "no" let (big, small): (Vec<i32>, Vec<i32>) = orders.into_iter().partition(|p| *p >= 30); println!("big {big:?}, small {small:?}");
// find_map: try to parse each one, keep the first that works let inputs = ["abc", "", "42", "7"]; let first_number = inputs.iter().find_map(|s| s.parse::<i32>().ok()); println!("first number: {first_number:?}");
// flat_map: every line becomes several words, all in one list let lines = ["buy milk", "call mum now"]; let words: Vec<&str> = lines.iter().flat_map(|line| line.split(' ')).collect(); println!("{words:?}");}Output:
total 144, with fees 154first order over 50 is at index Some(3)big [40, 55, 30], small [12, 7]first number: Some(42)["buy", "milk", "call", "mum", "now"]fold takes a starting value (0) and a closure that gets “the result so far” and the next item. If all you need is a plain total, sum says it more clearly. Reach for fold when the rule is your own.
Cleaning up a Vec in place: dedup and retain
Section titled “Cleaning up a Vec in place: dedup and retain”These two are Vec methods, not iterator methods, but they belong in the same drawer. dedup is like a DJ who never plays the same song twice in a row (the song can come back later). retain is a bouncer: only items that pass the test stay inside.
fn main() { // dedup removes repeats that sit next to each other let mut clicks = vec!["home", "home", "shop", "shop", "shop", "cart", "home"]; clicks.dedup(); println!("{clicks:?}");
// retain keeps only the items that pass the test let mut cart = vec![("apple", 3), ("bread", 0), ("milk", 1), ("eggs", 0)]; cart.retain(|(_, qty)| *qty > 0); println!("{cart:?}");}Output:
["home", "shop", "cart", "home"][("apple", 3), ("milk", 1)]The last "home" survived because it wasn’t next to another "home". To remove every duplicate, sort first, as the Sorting page shows.
Use it when you want to change a Vec without building a new one.
Option and Result conveniences
Section titled “Option and Result conveniences”An Option is like a parcel that may or may not have arrived. You could open every parcel with a full match, but most of the time a one-word helper says the same thing. The Option page covers unwrap_or and match; here are the next helpers.
fn main() { let nickname: Option<String> = None; let age: Option<u32> = Some(17); let width: Option<u32> = Some(3); let height: Option<u32> = Some(4);
// unwrap_or_default: the value, or the type's default ("", 0, empty Vec...) println!("nickname: '{}'", nickname.unwrap_or_default());
// map_or: a fallback if None, otherwise transform the value println!("{}", age.map_or("age unknown".to_string(), |a| format!("{a} years old")));
// filter: keep the value only if it passes a test println!("adult age: {:?}", age.filter(|a| *a >= 18));
// zip: Some only if BOTH are Some println!("area: {:?}", width.zip(height).map(|(w, h)| w * h));
// and_then: chain a step that can itself give None let good = Some("42").and_then(|s| s.parse::<u32>().ok()); let bad = Some("forty").and_then(|s| s.parse::<u32>().ok()); println!("parsed: {good:?} and {bad:?}");}Output:
nickname: ''17 years oldadult age: Nonearea: Some(12)parsed: Some(42) and NoneInside a function, ? works on Option too: if the value is None, the function returns None right away. In a function that returns a Result, turn the Option into a Result first with ok_or(error) or ok_or_else(|| error). Use ok_or_else when building the error takes work, because it only runs when needed.
struct Employee { name: String, manager: Option<String>,}
// `?` on an Option: if it's None, return None right away.fn manager_initial(e: &Employee) -> Option<char> { let manager = e.manager.as_ref()?; manager.chars().next()}
// ok_or_else turns None into an error, so `?` works in a Result function.fn port_from(setting: Option<&str>) -> Result<u16, String> { let raw = setting.ok_or_else(|| "PORT is not set".to_string())?; raw.parse::<u16>().map_err(|e| e.to_string())}
fn main() { let ana = Employee { name: "Ana".to_string(), manager: Some("Grace".to_string()) }; let grace = Employee { name: "Grace".to_string(), manager: None }; println!("{}: {:?}", ana.name, manager_initial(&ana)); println!("{}: {:?}", grace.name, manager_initial(&grace));
println!("{:?}", port_from(Some("8080"))); println!("{:?}", port_from(None)); println!("{:?}", port_from(Some("eighty")));
// transpose: Option<Result<..>> becomes Result<Option<..>> let retries_setting: Option<&str> = Some("3"); let retries: Result<Option<u8>, _> = retries_setting.map(|s| s.parse::<u8>()).transpose(); println!("{retries:?}");}Output:
Ana: Some('G')Grace: NoneOk(8080)Err("PORT is not set")Err("invalid digit found in string")Ok(Some(3))transpose is rare, but it is the neat answer to “I have an optional setting, and parsing it can fail”. With it, you can put ? on the result and keep the Option.
Use it when a match on an Option has one interesting arm and one boring one.
The entry API on HashMap
Section titled “The entry API on HashMap”Think of a coat check desk. You say a name, and the attendant either finds that hook or puts up a new one, then hands it to you. One trip, not two. That’s .entry(key). The HashMaps page shows entry(key).or_insert(value), the classic counter. Two more variations are worth knowing:
or_insert_with(f)only builds the default when the key is missing.or_default()does the same with the type’s default.and_modify(f)changes the value if the key exists; chainor_insertafter it for the “new key” case.
use std::collections::HashMap;
fn main() { let sales = [("apples", 3), ("pears", 2), ("apples", 5), ("plums", 1), ("pears", 4)];
// and_modify + or_insert: update if present, insert if not let mut totals: HashMap<&str, i32> = HashMap::new(); for (fruit, qty) in sales { totals.entry(fruit).and_modify(|t| *t += qty).or_insert(qty); }
// or_insert_with: create the empty Vec only when it's needed let mut orders: HashMap<&str, Vec<i32>> = HashMap::new(); for (fruit, qty) in sales { orders.entry(fruit).or_insert_with(Vec::new).push(qty); }
// HashMap order is random, so sort the keys before printing let mut fruits: Vec<&str> = totals.keys().copied().collect(); fruits.sort(); for fruit in fruits { println!("{fruit}: total {}, orders {:?}", totals[fruit], orders[fruit]); }}Output:
apples: total 8, orders [3, 5]pears: total 6, orders [2, 4]plums: total 1, orders [1]Use it when you would otherwise write “if the key exists, change it, else insert it”, which looks the key up twice.
The never type !: code that never comes back
Section titled “The never type !: code that never comes back”Some roads are one-way: once you drive in, you never come back out. In Rust, an expression that never finishes has the never type, written !. panic!, return, break, continue, an endless loop and std::process::exit all have this type.
Because a ! value can never actually exist, the compiler lets it stand in for any type. That’s why a match can have one arm that gives an i32 and another that does return: the second arm never produces a value, so it can’t break the types.
use std::process;
// `-> !` means "this function never returns"fn quit(message: &str) -> ! { println!("{message}"); process::exit(0)}
fn level_points(level: &str) -> u32 { let points: u32 = match level { "easy" => 10, "hard" => 30, _ => return 0, // `return` has type `!`, so it fits where a u32 is expected }; points * 2}
fn main() { println!("easy: {}, hard: {}, weird: {}", level_points("easy"), level_points("hard"), level_points("weird"));
let mut total = 0; for input in ["4", "oops", "6"] { let n: i32 = match input.parse() { Ok(n) => n, Err(_) => continue, // `continue` is also `!` }; total += n; } println!("total: {total}");
let config_file: Option<&str> = None; let config = match config_file { Some(path) => path, None => quit("no config file found, stopping"), }; println!("loaded {config}"); // never printed}Output:
easy: 20, hard: 60, weird: 0total: 10no config file found, stoppingOne warning: process::exit stops the program immediately, without running cleanup code (destructors) for values that are still alive. In real programs, returning from main is usually the gentler choice.
Two macros use ! as placeholders while you sketch code. todo!() means “I’ll write this later”. unimplemented!() means “this isn’t supported here”. Both panic if they are ever reached, but they let the rest of your program compile:
fn export_pdf(report: &str) -> Vec<u8> { todo!("PDF export comes in v2")}If you call it, you get not yet implemented: PDF export comes in v2. With unimplemented!("PDF export") the message is not implemented: PDF export.
Use it when a function should stop the program, or a branch should leave early instead of giving a value.
Small ergonomic wins
Section titled “Small ergonomic wins”These are the pocket tools: tiny, but you’ll use them every week.
matches!(value, pattern)answers “does this fit the pattern?” withtrueorfalse(more on the Pattern Matching page).std::cmp::min/maxpick the smaller or larger of two values, and.clamp(low, high)keeps a value inside a range, like a volume knob that stops at 100..abs_diff(other)gives the distance between two numbers. It’s always positive, and it never overflows, even for unsigned types.dbg!(expr)prints the file, line, expression and value, then hands the value back, so you can wrap it around anything.assert!(condition)panics if the condition is false.debug_assert!does the same, but only in debug builds, so it costs nothing in release builds.
use std::cmp::{max, min};
enum Order { Pending, Shipped, Delivered, Cancelled,}
fn main() { let orders = [Order::Pending, Order::Delivered, Order::Shipped, Order::Cancelled, Order::Pending];
// matches!: a yes/no pattern check, with | for "or" let open = orders.iter().filter(|o| matches!(o, Order::Pending | Order::Shipped)).count(); println!("open orders: {open}");
// min, max and clamp println!("min {}, max {}", min(7, 3), max(7, 3)); let volume = 140; let safe = volume.clamp(0, 100); println!("volume {volume} clamped to {safe}");
// abs_diff: how far apart, never negative let (guess, secret): (u32, u32) = (30, 42); println!("you are {} away", guess.abs_diff(secret));
// dbg! prints to stderr and gives the value back let doubled = dbg!(safe * 2);
// assert! stops the program if this is ever false assert!(doubled <= 200, "doubled volume too high: {doubled}"); debug_assert!(safe <= 100); // only checked in debug builds println!("all checks passed");}Output:
open orders: 3min 3, max 7volume 140 clamped to 100you are 12 awayall checks passedThe dbg! line isn’t in the output above, because dbg! writes to stderr, not stdout. In the Playground it shows up in the “Standard Error” panel:
[src/main.rs:28:19] safe * 2 = 200That’s also why dbg! is safe to sprinkle around while debugging: it doesn’t mix into your program’s real output. Just remember to remove it before you ship.
Sorting has its own small tools. The Sorting page covers them in depth; here is the short version. sort_unstable is a little faster than sort and fine for plain numbers. sort_by_key sorts by one field. binary_search finds an item in sorted data very quickly, like opening a dictionary in the middle instead of reading from page one.
struct Player { name: &'static str, score: u32,}
fn main() { // sort_unstable: a bit faster; equal items may swap places let mut ids = vec![42, 7, 19, 3, 25]; ids.sort_unstable(); println!("{ids:?}");
// binary_search: Ok(index) if found, Err(index) where it would go println!("find 19: {:?}", ids.binary_search(&19)); println!("find 20: {:?}", ids.binary_search(&20));
// sort_by_key: sort by one field let mut players = vec![ Player { name: "Ana", score: 120 }, Player { name: "Ben", score: 95 }, Player { name: "Chen", score: 150 }, ]; players.sort_by_key(|p| p.score); let names: Vec<&str> = players.iter().map(|p| p.name).collect(); println!("lowest to highest: {names:?}");}Output:
[3, 7, 19, 25, 42]find 19: Ok(2)find 20: Err(3)lowest to highest: ["Ben", "Ana", "Chen"]Err(3) isn’t a failure to worry about. It tells you that 20 isn’t in the list, and that index 3 is where you could insert it to keep the list sorted.
Use it when you catch yourself writing a small if or loop that one of these already does.
Common mistakes
Section titled “Common mistakes”Using mem::take on a type without Default. mem::take has to leave something behind, so it needs to know what “empty” means for the type:
use std::mem;
struct Connection { host: String,}
struct Pool { active: Connection,}
impl Pool { fn reset(&mut self) -> Connection { mem::take(&mut self.active) // ❌ what would an "empty" Connection be? }}error[E0277]: the trait bound `Connection: Default` is not satisfied --> src/main.rs:13:19 |13 | mem::take(&mut self.active) // ❌ what would an "empty" Connection be? | --------- ^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Connection` | | | required by a bound introduced by this call |help: consider annotating `Connection` with `#[derive(Default)]`Fix it by choosing the replacement yourself with mem::replace(&mut self.active, Connection { host: "db2".to_string() }), or by storing the field as Option<Connection> and using self.active.take(). Only add #[derive(Default)] if an “empty” value really makes sense for the type.
Using ? on an Option in a function that returns Result. The two don’t mix automatically:
fn port_from(setting: Option<&str>) -> Result<u16, String> { let raw = setting?; // ❌ None has no error message to return raw.parse::<u16>().map_err(|e| e.to_string())}error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result` --> src/main.rs:2:22 |1 | fn port_from(setting: Option<&str>) -> Result<u16, String> { | ---------------------------------------------------------- this function returns a `Result`2 | let raw = setting?; // ❌ None has no error message to return | ^ use `.ok_or(...)?` to provide an error compatible with `Result<u16, String>`The compiler already tells you the fix: setting.ok_or("PORT is not set".to_string())?, or ok_or_else as in the example above. See E0277 for other forms of this error.
Expecting dedup or binary_search to work on unsorted data. dedup only removes repeats that are next to each other, and binary_search on unsorted data gives a meaningless answer. Neither one is a compiler error, so it’s easy to miss. Sort first.
Forgetting the leftovers of chunks_exact. A for loop over chunks_exact(3) silently skips the last 1 or 2 items. If they matter, handle .remainder(), or use chunks instead.
Practice
Section titled “Practice”1. Daily price changes
Section titled “1. Daily price changes”You have the prices [100, 104, 98, 110, 107]. Build a Vec with the change from each day to the next, print it, then print the biggest rise. Hint: windows(2) gives each pair as a small slice w, so the change is w[1] - w[0]. .iter().max() finds the biggest.
Show solution
fn main() { let prices = [100, 104, 98, 110, 107];
let changes: Vec<i32> = prices.windows(2).map(|w| w[1] - w[0]).collect(); println!("changes: {changes:?}");
let biggest_rise = changes.iter().max(); println!("biggest rise: {biggest_rise:?}");}Output:
changes: [4, -6, 12, -3]biggest rise: Some(12)Five prices give four changes, because windows(2) makes one pair per step. max() returns an Option, since an empty list has no biggest value.
2. Flush the print queue
Section titled “2. Flush the print queue”Write a Printer struct with a queue: Vec<String> field. Give it a method add(&mut self, file: &str) and a method flush(&mut self) -> Vec<String> that hands back every queued file and leaves the queue empty. Add two files, flush, and print what was flushed and how many files are left. Hint: flush only has &mut self, so use std::mem::take.
Show solution
use std::mem;
struct Printer { queue: Vec<String>,}
impl Printer { fn add(&mut self, file: &str) { self.queue.push(file.to_string()); }
fn flush(&mut self) -> Vec<String> { mem::take(&mut self.queue) }}
fn main() { let mut printer = Printer { queue: Vec::new() }; printer.add("report.pdf"); printer.add("photo.png");
let jobs = printer.flush(); println!("printing {} jobs: {jobs:?}", jobs.len()); println!("left in queue: {}", printer.queue.len());}Output:
printing 2 jobs: ["report.pdf", "photo.png"]left in queue: 0Writing self.queue on its own in flush would fail with E0507. mem::take moves the Vec out and leaves an empty one behind, with no copying.
3. The first valid port
Section titled “3. The first valid port”Write a function first_port(candidates: &[&str]) -> Option<u16> that returns the first string that parses as a u16. Call it with ["", "abc", "70000", "8080", "9090"] and with ["none", "-1"], and print the port, or 3000 if there is none. Hint: find_map with s.parse::<u16>().ok(), then unwrap_or(3000).
Show solution
fn first_port(candidates: &[&str]) -> Option<u16> { candidates.iter().find_map(|s| s.parse::<u16>().ok())}
fn main() { let from_config = ["", "abc", "70000", "8080", "9090"]; let all_bad = ["none", "-1"];
println!("using port {}", first_port(&from_config).unwrap_or(3000)); println!("using port {}", first_port(&all_bad).unwrap_or(3000));}Output:
using port 8080using port 3000"70000" looks like a number, but it doesn’t fit in a u16 (the largest is 65535), so parsing fails and find_map moves on. It stops at "8080" and never even looks at "9090".
mem::take,mem::replaceandOption::takemove a value out of a&mutby leaving something in its place;mem::swaptrades two values.- On slices,
windowsgives overlapping neighbours,chunksgives batches, andsplit_first/split_lastgive “one item and the rest”. peekablelets you look ahead;zip,enumerate,chain,fold,find_mapandpartitionreplace many hand-written loops.Optionhelpers likeunwrap_or_default,map_or,filterandok_orsave you from writing smallmatches, and?works onOptiontoo.entry()withor_insert_withorand_modifyupdates aHashMapin one lookup.- The never type
!belongs to code that never finishes (panic!,return,continue,process::exit), so it fits anywhere a value is expected.
Quick check
Section titled “Quick check”Quick check
1. What does mem::take(&mut self.items) leave behind when items is a Vec?
2. What does [1, 2, 3, 4].windows(2) give you?
3. Why does let x: i32 = match s { "a" => 1, _ => return }; compile?
4. Your function returns a Result, but you have an Option. What do you add before ?
Score: 0 / 4
Read more
Section titled “Read more”std::mem— standard library docs:take,replace,swapand the other memory helpers, with examples.- Slice methods — standard library docs: every slice method, including
windows,chunks,split_firstandbinary_search. Iteratortrait — standard library docs: the full list of iterator methods, fromzipandpeekabletopartitionandfind_map.Option— standard library docs: all theOptionhelpers, grouped by what they do.- Diverging functions — Rust by Example: short examples of functions that return
!and why that is useful.