Skip to content

What to cover next — the strings backlog

Level: reference · the backlog

One line: Three backlogs for the strings chapter, all filed by Adam — 198 terms that should each be answerable somewhere, a progression that puts the existing lessons in teaching order, and 20 katas that supply the practice neither of the other two has.

# Backlog What it is The work in it
1 The vocabulary 198 terms in 15 groups Mostly a glossary sweep — done 2026-09-10
2 The progression 8 steps, 8 exercises, a mental model One ordering decision, then exercises — done 2026-09-10
3 The twenty katas Graded hello() → Levenshtein, as test cases ~14–18 new programs — the real build — done 2026-09-10

The first is a dictionary, the second a route, the third the practice. They overlap on purpose; where two of them ask for the same page, the later section says so and names the merge.

The job, for Thursday 2026-09-10 — backlog 1

Result — done 2026-09-10. All 198 are answerable. By where the first link goes: 122 to a lesson, 63 to a page of the method reference, 10 to a stub (the term is on a page, but no example backs that page yet — 120 and 12 on the day of the sweep, until String parameters worth copying graduated later that day), and 3 to a page of the sibling encodings library, which owns the Unicode theory this one leans on. 65 terms gained a glossary entry. The fifteen crates share one new page, The string crates, since none of them can run here. Three things the sweep found are worth more than the ticks: the stub for String parameters worth copying claimed PathBuf satisfies impl AsRef<str>, and it does not (E0277; corrected in the same sweep); std::string::ParseError is a type alias for Infallible that no page mentioned; and Pin<Box<str>> compiles and pins nothing, because str is Unpin — both measured on 1.98.0.

Adam's ask, in his words: "make sure we have a page for all these terms (even if a stub)." The list is below, unedited apart from grouping and code formatting.

Audit before writing anything. 14_Strings/ already holds 33 lessons and STRINGS.md already maps them, so the honest first pass is a sweep that says, per term, which of three states it is in:

  1. A lesson teaches it — nothing to do but check GLOSSARY.md has the entry and it points at that lesson.
  2. The glossary defines it and links somewhere real — that is coverage. A term does not need a folder to be answerable.
  3. Neither — only then is there a page to write, and the next question is whether it is a page of its own or a paragraph on one that exists.

That order matters because of what a new folder costs. A folder name here is a permanent URL, so minting 198 stub folders would mint 198 permanent URLs before anyone knows what any of those pages says — and most of these terms are not lessons, they are vocabulary, which is what a glossary is for. Expect the sweep's answer to be mostly "glossary entry pointing at an existing lesson", with a short list of genuine gaps behind it.

Three things already known, so they do not get rediscovered

The crates group cannot have runnable examples. Every example in this library is bare rustc, no Cargo and no crates, so the fifteen crate names below can be described and cannot be demonstrated. That is a single survey page — what each crate is for, and what std makes you do without it — not fifteen stubs. The same page is where unicode-segmentation and unicode-normalization belong, and the sibling encodings library's corpus page already states the std gap they fill.

Some of these are one lesson, not several. Chars / CharIndices / Bytes are three types answering one question, and to_lowercase / to_uppercase / make_ascii_lowercase / eq_ignore_ascii_case are four names for one decision. Group the terms into the lesson that answers them and let the glossary carry the individual names — a page per method would be a reference manual, and docs.rs ↗ is already better at that than this library will ever be.

A few sit outside strings entirely. MaybeUninit, UnsafeCell, NonNull, data races and pointer alignment are ownership and unsafety topics that a string page would borrow rather than own. Check 18_Ownership and 09_Advanced before assuming they belong in 14_Strings.

The list

198 terms. A box is ticked when the term is answerable — a lesson, or a glossary entry that links to one — not when it has a folder.

Swept 2026-09-10. Every box is ticked, and the link beside each term is where its answer is — the first link is the one to read. glossary marks a term that gained an entry in GLOSSARY.md in the same sweep.

Core types (15)

Encodings (13)

Conversions (14)

Operations (13)

Traits a string type implements (15)

Memory and layout (11)

Raw and FFI (10)

Iterators and views (14)

Crates and ecosystem (15)

Unicode concepts (12)

Error types (6)

Common methods (33)

Pointer and safety (11)

Patterns and matching (9)

Smart pointers and wrappers (7)


Second backlog — the progression

Level: reference · the backlog

One line: Adam's step-by-step route from ownership to fluent string code, filed 2026-09-08 — a route rather than a dictionary, and the half of it this library has least of is the practice.

Result — done 2026-09-10. The route is a new section of STRINGS.md, Starting from nothing — option (b) below, so no new URL. It opens with the four-line model, lists the steps in prerequisite order, and turns the seven claims into its What you can say afterwards column, each against the page that states it. Step 8, the one this backlog says most needs finishing, is done: String parameters worth copying graduated from a stub into a lesson whose every claim is an allocation count, measured by a counting global allocator. Of the eight exercises, four were already answered by existing katas, and four are new katas on the pages their Rust point belongs to; a fifth new kata, on the graduated page, answers step 8's builder. The common-mistakes list is answered item by item, and the resources Adam names are now all on the resources page.

Where the vocabulary list asks "is this term answerable?", this asks a different question: can a person walk from nothing to fluent in a fixed order, and write a program at each stop? The library already has the lessons — 14_Strings/ holds 33 of them and STRINGS.md maps them by the question each answers. What it has never had is the order, stated as a path, with something to write at every step.

The same audit rule applies as above. Every "where it should land" below is a routing hypothesis, not a coverage claim — it names the page that ought to answer the item so the sweep can confirm or contradict it one page at a time. None of it has been read against the page's actual text yet. Tick a box when the page has been opened and it really does answer. All of it was read on 2026-09-10 — the states and ticks below are what that reading found.

The open question this backlog has to settle first

STRINGS.md already calls itself "the door to every lesson about it, in the order the questions come up" — so the library has a route already, and it is ordered by the question a reader arrives with. Adam's plan is ordered by prerequisite: ownership and memory before any string type, conversions before programs, programs before edge cases. These are two different orderings of the same 33 lessons, and both are defensible. So the first decision, before any page gets written:

  • (a) a new page — a study path that links the existing lessons in Adam's order and owns the exercises, leaving STRINGS.md as the by-question map; or
  • (b) a second table inside STRINGS.md — "if you are starting from nothing, read them in this order" — and no new URL at all.

(b) is cheaper and probably right for the route itself, since a folder name is a permanent URL. But the exercises below are the part that has no home under either option, and they are the part with real work in them. Decide this before minting anything.

Settled 2026-09-10 — (b). The route is the Starting from nothing section of STRINGS.md. The exercises did not need a route page after all: every one landed as a kata on its topic's page, which is where the house rule puts katas anyway.

Foundation first — the four prerequisites

Adam puts these before any string page, which the library agrees with: STRINGS.md already files them under "the lessons strings lean on".

Prerequisite Where it should land Note
Ownership & borrowing rules Ownership and moves · Borrowing Already the worked example half these pages use
Stack vs heap memory The anatomy of a String Three words on the stack, bytes on the heap
References vs values Borrowing
Copy vs Clone vs Move Copy vs Clone Why &str copies and one String field moves a whole struct
Slices (&[T]), and &str as &[u8] + a guarantee String slices · Arrays and slices Said on both: String slices"the only thing &str adds is a promise that the bytes are valid UTF-8" — and the anatomy page's section A Vec<u8> that promises UTF-8

The seven claims a reader must be able to state

Adam's "core concepts to master", as claims a reader should be able to make unprompted.

# The claim Where it should land
1 str is unsized — you can never own a bare str str is unsized
2 &str is a fat pointer: (data pointer, length) str is unsized — the fat pointer's second word
3 String is a growable heap buffer: (pointer, length, capacity) The anatomy of a String
4 String derefs to &str via Deref<Target = str> String vs &str
5 &String auto-coerces to &str (deref coercion) String vs &str
6 UTF-8 is variable width, 1–4 bytes Meet the char · Four lengths
7 Slicing by byte index panics off a char boundary String slices

Verified 2026-09-10. Each claim was checked against the page it routes to, and all seven now form the What you can say afterwards column of the route in STRINGS.md.

The eight steps

Step Adam's ask Where it should land Likely state
1 Read the official docs — std::string::String, std::str, std::primitive::str, Book ch. 4 and ch. 8 Strings: links, books and videos Covered — ch. 4.1 and 8.2 were named by number already; ch. 4.3 (slices) and ch. 10.3 (lifetimes) are added
2 Draw the memory: String on the heap vs &str into read-only memory; compare String::from("hello") / "hello" / &String::from("hello") The anatomy of a String · &'static str Covered — the three-way picture is now a kata, Five bindings, two buffers
3 Practise the conversions — &String&str, String&str, &strString four ways, String&[u8], Vec<u8>String Making a String · String vs &str Coveredfrom_utf8 and as_bytes live in the method reference, and the route links it
4 Write small programs — take &str return String; take String return &str (the lifetime trap); &str in a struct; build with push_str vs format! vs + Building a String · Concatenating strings · How to learn lifetimes Covered — by the katas on String vs &str, Building a String and &'static str, and by the graduated signature lesson
5 Edge cases — "héllo", "日本語", "🦀", .len() vs .chars().count(), the boundary panic, .get(0..3), byte vs char vs grapheme Meet the char · Four lengths · Walking a String Covered
6 Cow<str> — functions that sometimes borrow and sometimes own; String::from_utf8_lossy() Cow: borrow until somebody writes Covered, with one gap closedfrom_utf8_lossy was not on the Cow page; it is now linked from it and from the route
7 Study the APIs — every method on str, every method on String, the pattern-based ones str methods (83) · String methods (42) Covered
8 Common patterns — builder, &str parameters, String returns, impl Into<String> / impl AsRef<str> String parameters worth copying DoneString parameters worth copying graduated: every signature's cost is an allocation count, and its kata is the builder, A builder that moves, not copies

The eight exercises

This is where the plan actually adds work. KATAS.md had 170 katas when this was filed; a grep for these found one.

# Exercise State Note
1 Memory — without running, where does each of ae live, and what is its size? New kata Five bindings, two buffers — handles in machine words, so the answer holds on any target, and as_ptr() for which names share bytes
2 Lifetimes — why does fn longest(a: &str, b: &str) -> &str not compile, and how is it fixed? Already answered By Return a palindrome you did not copy, built in backlog 3 — its two-input longer_palindrome is this longest — and by Pivot both ways, then earn E0106 and E0515 on String vs &str, whose borrowed User field is the struct half of the same question
3 UTF-8 — what does "🦀" print for .len() and .chars().count(), and what happens at &s[0..1]? Already answered By Two rulers over one string on Walking a String, which counts "Hello 🦀!" both ways, and by the slicing katas on String slices
4 Ownership transfer — which of take_string(s) / take_str(&s) / take_str(s.as_str()) / take_string(&s) compile? Already answered By One &str parameter, three callers — then flip it on String vs &str. take_string(&s) measured as E0308
5 Build a CSV parser — lines from a &str, split on commas, handle quoted fields, trim, return Vec<String> New kata A CSV reader, and the newline inside the quotes — beside the tokenizer, with the opposite rule about empty fields
6 Implement to_camel_case&str in, String out, handling spaces, underscores and hyphens Already answered By Case and whitespace on Meet the char — the same finding as backlog 3's kata 8
7 Zero-copy logger — store log lines as &str into a shared buffer New kata A log that keeps views, not copies — std only, as planned: 1,009 allocations for a String per line against 9 for views, and the self-referential struct's E0515 and E0505
8 Tiny string interner — dedupe into a Vec<String>, hand back &str handles New kata An interner that hands out numbers — the &str-returning version's E0499, then a Symbol(u32); the Rc<str> kata beside it stays as it was

Common mistakes — Adam's list

Worth keeping as a checklist even where a lesson covers it, because this is the set a reviewer scans for.

  • [x] Returning &str that points into a local String — answered: the E0106-then-E0515 pair, on String parameters worth copying and in the label kata on &'static str
  • [x] Slicing at a non-char boundary — answered: String slices
  • [x] Comparing String with &str incorrectly — answered: measured on 1.98.0: s == "a", "a" == s, &s == "a" and s.as_str() == "a" all compile; only comparing against a char or a &&str fails, as E0277. See Comparing and sorting text
  • [x] Forgetting .as_str() where it is needed — answered: match on a bare String is E0308; String::as_str shows the match that needs it
  • [x] + with two &str — the left operand must be an owned String (Concatenating strings is the whole of this one) — answered, with a kata on that page that earns the E0369 on purpose
  • [x] Assuming .len() counts characters — it counts bytes — answered: Meet the char and Four lengths
  • [x] Storing &str in a struct without a lifetime parameter — answered: Pivot both ways, then earn E0106 and E0515 on String vs &str, whose borrowed User field is E0106
  • [x] Reaching for String where &str would do — an allocation nobody asked for — answered: String parameters worth copying, section 2: a String parameter makes two of three callers allocate

The mental model, as Adam states it

String  =  owned, mutable, heap, like Vec<u8> + UTF-8 valid
&str    =  borrowed, immutable, points to String/static/other
str     =  unsized type, never directly held, only behind reference
String literal = &'static str

This is the same "owner and view" pattern STRINGS.md opens with, said in four lines. It now opens the Starting from nothing section of STRINGS.md, with one correction: str lives behind a pointer&str, Box<str>, Rc<str> — not only behind a reference.

Rule of thumb

  • Function parameter: &str, unless you need to consume it
  • Function return: String if it is new data, &str if it borrows the input (with lifetimes)
  • Struct field: String — rarely &str, and only with a lifetime parameter
  • Temporary: &str for read-only access

This is now the closing section of String parameters worth copying, where the parameter and return lines are backed by measured allocation counts.

Resources Adam names

Check these against Strings: links, books and videos and add whatever is missing:

  • The Rust Book — ch. 4 (ownership), ch. 8 (strings), ch. 10 (lifetimes)
  • Rust by Example — the Strings section
  • Programming Rust (O'Reilly) — the text chapter
  • The std source: library/alloc/src/string.rs, library/core/src/str/ (the plan's src/liballoc/… paths are pre-2020 names — use the current ones)
  • rustc --explain for E0106, E0506, E0716ERRORS.md already carries the last two
  • Practice sets: Advent of Code string-heavy days, the Exercism Rust track

Checked 2026-09-10. Strings: links, books and videos already had Rust by Example's strings page, Programming Rust ch. 17 and the Exercism track. Added: Book ch. 4.3 and 10.3, the library/alloc/src/string.rs and library/core/src/str/ source paths, the online error index for E0106 and E0515 (what rustc --explain prints), and Advent of Code.


Third backlog — the twenty katas

Level: reference · the backlog

One line: Adam's graded set of 20 string katas, filed 2026-09-08, from hello() to Levenshtein — the practice half the progression above asks for, already written as test cases.

These arrived as #[cfg(test)] mod tests blocks with the body left as // Your code here, which is the right shape for a kata: the tests are the specification. KATAS.md currently holds 170; these would be K171–K190 if all twenty are taken.

The shape question is already settled — do not re-argue it

Adam's katas are written in cargo test form, and this library has no Cargo. That is not a problem, and the precedent is already here:

  • rustc --edition 2024 --test <file>.rs builds the harness on a loose file — no Cargo, no crates. It is defined in GLOSSARY.md, taught on Running a scratch program, and used by Where a test goes.
  • But the diffed .out artefact cannot come from the harness. The test harness runs in parallel, so its output order varies — Where a test goes says so in the title of its own fence, and its recorded .out is a main() run, not a harness run.

So the house pattern for all twenty is the one where_a_test_goes.rs already uses: keep Adam's #[test] functions, and add a main() that walks the same cases in fixed order and prints them. The tests give the reader cargo test-shaped practice; the main() gives the library its byte-stable recorded output. A harness run may still appear on the page as a labelled fence, never as the diffed artefact.

Result — done 2026-09-10. Half of the twenty were already katas in this library, and got a link rather than a copy: a duplicate kata is worse than none, since a reader meets both and learns nothing from the second. The other ten are new — ten programs, each keeping the original #[test] functions as the specification and adding a main() whose output is the recorded answer. Each also demonstrates the Rust trap under its algorithm, which is usually the unit: bytes or chars. Every one passes its tests under rustc --test, and one of them only after correcting a test (finding 5). The Lands on column below now says which is which.

Four things found reading the twenty

Recorded so they are not rediscovered one at a time while writing.

  1. Kata 1 has nothing to fix. The body already returns "Hello, World!" and the test already passes; the // Make it work comment has no bug behind it. Either give it a real defect ("Hello World!", a missing comma — the assertion then earns its keep) or drop it and start the set at Kata 2.

  2. Kata 7 is str::replace with a different name. As written the answer is one line, so it teaches nothing. It needs the constraint Kata 4 already has — Kata 4 says "without using .rev()", and Kata 7 should say "without using replace()". Then it becomes a real exercise in find, byte offsets and push_str, and it pairs with Replacing part of a string, whose lesson is that a chain of replaces is not a substitution table.

  3. Kata 18 asks for graphemes and tests for chars. It says "count grapheme clusters (user-perceived characters) … without external crates", but every assertion — "hello" 5, "héllo" 5, "日本語" 3 — is satisfied by .chars().count(), because that é is one precomposed scalar. The test cannot tell the two answers apart, so the stated goal and the specification disagree. Worse, the two are genuinely different work: real grapheme clustering is UAX #29, which is what unicode-segmentation exists for and which the no-crates rule forbids. Fix it one of two ways — rename it to count_chars and keep it easy, or keep the name and add a decomposed case ("he\u{301}llo", or a flag, or a ZWJ family emoji) and scope it honestly to a subset of UAX #29. Four lengths is the page that already draws this distinction, and it should be the kata's home either way.

  4. Four of them are algorithm katas wearing string clothes. Kata 13 (regex), 14 (word ladder, a BFS), 15 (interleaving, a DP) and 20 (edit distance, a DP) exercise dynamic programming and graph search; the string is the input, not the subject. That is fine — but this library's katas are about Rust's behaviour, and these would be the first that are not. Decide whether they join the set, go to a separate "algorithms on text" group, or stay out. Kata 16 is the counter-example and should definitely be in: fn longest_palindrome(s: &str) -> &str returns a slice borrowed from its input, which is lifetime elision doing exactly the thing Step 4 of the progression is about.

  5. One test was wrong. Kata 15 asserted !is_interleave("ab", "cd", "acbd"), but a, c, b, d takes a and b from "ab" and c and d from "cd", each in order — so it is an interleaving, and a correct solution fails the original test. The published kata corrects the line, keeps the original in a comment, adds a genuine negative ("bacd"), and prints all six interleavings of "ab" and "cd" to settle it.

How the five were settled. (1) Kata 1 is already covered by an existing kata, so its missing defect no longer matters. (2) Kata 7 went in with the "without replace()" constraint. (3) Kata 18 is already covered by "The third ruler", which does the honest version: a std-only grouper, and a statement of what it still cannot do. (4) All four algorithm katas joined, each on the page its Rust angle belongs to rather than in a separate group — the regex on searching, the ladder on HashSet, interleaving on slices, edit distance on lengths — because each one turned out to have a Rust point under the algorithm: which unit you index by, and what a lookup or a slice borrows. (5) Corrected, as above.

The twenty

Adam's levels, kept as he graded them. "Lands on" is a routing hypothesis, same rule as everywhere above.

Beginner

# Kata What it exercises Lands on
1 hello() returns "Hello, World!" &strString Already a kataLet the source pick the spelling, the original body already passes its test, as finding 1 said, so there was nothing to add
2 full_name(first, last) with a space between format! vs +; the ("", "") case proves the space is unconditional Already a kataGreet two people three ways, the one thing the original adds is full_name("", "") giving " "
3 is_blank(s) — empty or all whitespace trim().is_empty(), and that "\t\n" is whitespace Already a kataCase and whitespace, which trims spaces, a tab and newlines; is_blank is that trim plus is_empty

Intermediate

# Kata What it exercises Lands on
4 reverse_string(s) without .rev() Manual iteration over chars(). Worth adding a note that reversing by char breaks combining marks — the ASCII tests hide it Already a kataThe third ruler, which reverses café by char and shows the accent landing on the wrong letter — the note this row asked for
5 word_count(s)" multiple spaces " is 2 The exact split(' ') vs split_whitespace() split that page is about Already a kataFind it without a regex engine, which writes this word_count with split_whitespace(); "An empty field is data", on the same page, is the split(' ') half
6 is_palindrome(s) ignoring case and punctuation filter, to_lowercase, is_alphanumeric; the ASCII-vs-Unicode case decision Already a kataFind it without a regex engine, whose palindrome checker survives punctuation, case and multibyte letters
7 replace_all(s, from, to) find + byte offsets + push_stronce the "without replace()" constraint is added Newreplace, without replace, with the "without replace()" constraint from finding 2, plus the empty pattern the original tests never try

Upper intermediate

# Kata What it exercises Lands on
8 to_camel_case — snake, kebab, already-camel Same exercise as Exercise 6 of the progression — merge them, do not write both Already a kataCase and whitespace, which converts MyVariableName both ways; the original adds kebab-case input. So the progression's Exercise 6 is covered too
9 compress_string — run-length, return original if longer Building with push/push_str, and a length comparison that is in bytes Already a kataRun-length encoding, and the input that breaks it, the original writes the character before the count (a2b1c5a3) and returns the input when compressing makes it longer
10 are_anagrams(s1, s2) Counting chars into a map; "debit card" / "bad credit" counts the space too Already a kataCase and whitespace, whose anagram checker ignores case and whitespace; "debit card" / "bad credit" passes either way

Advanced

# Kata What it exercises Lands on
11 longest_common_prefix(&[&str]) Slices of slices, and the &[] empty case NewThe longest common prefix, by letters, plus a borrowed &str version and the shared byte that is not a shared letter
12 permutations(s) — unique, "" yields one Recursion producing Vec<String>; dedup NewEvery distinct permutation, in one buffer, one String grown with push and shrunk with pop
13 is_match(s, pattern). and * DP / recursion — see finding 4 NewA regex engine with two operators, slice patterns on &[char]; the . that means half an é over bytes
14 word_ladder_length(...) BFS — see finding 4 NewA word ladder, and the key a lookup hands back, a HashSet<&str> probed from a scratch buffer

Expert

# Kata What it exercises Lands on
15 is_interleave(s1, s2, s3) DP — see finding 4 NewInterleaving, with the slices as the state, one original assertion was wrong — see finding 5
16 longest_palindrome(s) -> &str Returning a slice borrowed from the input — lifetime elision, and the best kata in the set for this library NewReturn a palindrome you did not copy, elision with one input, then E0106 with two
17 tokenize(input, &[char]) with quoted tokens Near-duplicate of Exercise 5, the CSV parser — merge, or make one the multi-delimiter variant of the other NewA tokenizer with quotes, and the error it cannot return, also answers the progression's Exercise 5 quoted-field rule
18 count_characters(s) See finding 3 — the goal and the tests disagree Already a kataThe third ruler, which writes a std-only grapheme grouper — the honest version of finding 3. The original tests pass with chars().count(), the answer that kata shows is wrong
19 justify_text(words, width) Padding and width arithmetic; the natural door to the format mini-language's {:<} / {:^} / {:width$} NewFull justification, and the width that is not bytes, widths counted in chars, checked against {:<16}
20 edit_distance(s1, s2) DP — see finding 4; note the distance is over chars, not bytes NewEdit distance, in the unit you choose, one generic table, run on chars and on bytes

What the three backlogs add up to

  • The vocabulary list is mostly a GLOSSARY.md sweep — few new pages.
  • The progression is mostly an ordering decision — one page, or one table inside STRINGS.md.
  • The katas are the real build: after merging the duplicates (8 with Exercise 6, 17 with Exercise 5) and settling the four algorithm katas, roughly fourteen to eighteen new programs, each with #[test] functions, a main(), and a recorded .out. (It came to ten: the other ten were already katas here — see the result above.)

Take them in Adam's order. Katas 2–10 are cheap and each one lands on a lesson that already exists, so the early ones cost a program and no prose.