Step 3 of 7
Splitting: lines, split, splitn, split_once
Most text a program receives is structured: lines, fields separated by commas or tabs, key=value pairs. Splitting is how you take it apart.
fn main() {
let order = "A12,Crab plush,4\nB07,Rust mug,12\nC33,Sticker pack,1";
let mut total = 0;
for line in order.lines() {
let fields: Vec<&str> = line.split(',').collect();
let qty: u32 = fields[2].parse().expect("qty must be a number");
total += qty;
println!("{} x {}", qty, fields[1]);
}
println!("{total} items");
}
4 x Crab plush
12 x Rust mug
1 x Sticker pack
17 items
lines()splits text into lines. It handles both\nand Windows\r\nendings and doesn't produce an empty last line for a trailing newline.split(',')gives the pieces between commas..collect()gathers them into aVec<&str>. The pieces are slices pointing into the original text: nothing is copied.fields[2].parse()turns the text"4"into a number.parseworks for any type that knows how to read itself from text (integers, floats,bool,char), and returns aResultbecause the text might not be a valid number.
The splitting family
fn main() {
println!("{:?}", "a,,b".split(',').collect::<Vec<&str>>());
println!("{:?}", " a b ".split_whitespace().collect::<Vec<&str>>());
println!("{:?}", "ERROR: disk full: /var".splitn(2, ": ").collect::<Vec<&str>>());
println!("{:?}", "port=8080".split_once('='));
println!("{:?}", "report.final.pdf".rsplit_once('.'));
println!("{:?}", "a;b,c".split([',', ';']).collect::<Vec<&str>>());
}
["a", "", "b"]
["a", "b"]
["ERROR", "disk full: /var"]
Some(("port", "8080"))
Some(("report.final", "pdf"))
["a", "b", "c"]
| Method | Splits | Notes |
|---|---|---|
split(sep) |
at every separator | keeps empty pieces ("a,,b" has an empty middle field) |
split_whitespace() |
at any run of spaces, tabs, newlines | never gives empty pieces |
lines() |
at line endings | |
splitn(n, sep) |
at most n pieces |
the last piece keeps any further separators |
split_once(sep) |
at the first separator | Option<(&str, &str)>: None if there's none |
rsplit_once(sep) |
at the last separator | file extensions, host:port |
The separator can be a char, a &str, an array of chars ([',', ';'], any of them) or a closure (|c: char| !c.is_alphanumeric()). collect::<Vec<&str>>() names the type in place (the "turbofish" ::<>) when there's no variable to annotate.
Common mistakes
split(' ')for words."a b".split(' ')gives["a", "", "b"]because of the double space. Usesplit_whitespace().- Indexing fields blindly.
fields[2]panics on a short line. For input you don't control, check the length or match onfields.as_slice()with a slice pattern (module 15). split('\n')on text ending in a newline gives an extra empty string at the end. Uselines().
Your task
Write fn parse_product(line: &str) -> Option<Product> for lines like A12, Crab plush, 4 (SKU, name, quantity). Trim spaces around each field. Return None unless there are exactly three fields and the quantity is a valid u32. Tip: let [sku, name, qty] = fields.as_slice() else { return None; };
Challenge 2
Write fn extension(filename: &str) -> Option<&str> that returns the text after the last dot: "photo.JPG" gives Some("JPG") and "archive.tar.gz" gives Some("gz"). Return None when there's no dot, when the name starts with its only dot (hidden files like .bashrc have no extension), or when the dot is the last character.
Challenge 3
Read a CSV grade sheet from input. The first line is the header name,score; every other line should be name,score. Trim spaces around each field. Skip rows that don't have exactly two fields (an empty line counts as one such row) or whose score isn't a whole number. There's always at least one valid row. Print:
students: 3
average: 84.7
best: ana (91)
skipped: 1
The average has one decimal. If two students share the best score, report the first one.
Previous: Slicing safely at char boundaries Next: Trimming and searching