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:
- A lesson teaches it — nothing to do but check GLOSSARY.md has the entry and it points at that lesson.
- The glossary defines it and links somewhere real — that is coverage. A term does not need a folder to be answerable.
- 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)¶
- [x]
str—stris unsized · glossary - [x]
String—Stringvs&str· The anatomy of aString - [x]
&str—Stringvs&str· String slices - [x]
&String—Stringvs&str· Coercion: the conversion you never write - [x]
Box<str>— The third owned form:Box<str>,Rc<str>,Arc<str>· glossary - [x]
Cow<str>—Cow: borrow until somebody writes - [x]
OsStr— Six kinds of string - [x]
OsString— Six kinds of string - [x]
CStr— Six kinds of string · Calling C — the call is free, the data is not - [x]
CString— Six kinds of string · Calling C — the call is free, the data is not - [x]
Path—PathandPathBuf(stub) · Six kinds of string · glossary - [x]
PathBuf—PathandPathBuf(stub) · glossary - [x]
char— Meet thechar· Why acharis 32 bits wide - [x]
u8— Meet the byte - [x] bytes — Meet the byte
Encodings (13)¶
- [x] UTF-8 — Meet the
char - [x] UTF-16 — Four lengths, and which one the other system means · glossary
- [x] UTF-32 — Why a
charis 32 bits wide · glossary - [x] ASCII — Meet the byte ·
str::is_ascii· glossary - [x] WTF-8 — Six kinds of string ·
OsStr,Path, and WTF-8 ↗ · glossary - [x] Unicode scalar value — Why a
charis 32 bits wide · glossary - [x] Unicode code point — Why a
charis 32 bits wide · glossary - [x] surrogate pairs — Why a
charis 32 bits wide · glossary - [x] byte order mark (BOM) —
String::from_utf16le· Byte order and the BOM ↗ · glossary - [x] little-endian — Meet the byte
- [x] big-endian — Meet the byte
- [x] locale encoding — Comparing and sorting text · Locale and
LC_CTYPE↗ · glossary - [x] platform encoding — Six kinds of string · glossary
Conversions (14)¶
- [x]
from_utf8—str::from_utf8·String::from_utf8 - [x]
from_utf8_lossy—String::from_utf8_lossy - [x]
to_string_lossy— Six kinds of string - [x]
as_bytes—str::as_bytes - [x]
as_os_str—PathandPathBuf(stub) - [x]
to_str— Six kinds of string - [x]
to_owned—ToOwned:Clonefor types whose owned twin is a different type - [x]
into_owned—Cow: borrow until somebody writes - [x]
to_string— Making aString - [x]
into_string—str::into_string - [x]
from_utf8_unchecked—str::from_utf8_unchecked - [x]
as_ptr—str::as_ptr - [x]
as_mut_ptr—str::as_mut_ptr - [x]
into_bytes—String::into_bytes
Operations (13)¶
- [x] slicing — String slices
- [x] indexing — Meet the
char - [x] concatenation — Concatenating strings
- [x] trimming —
str::trim - [x] splitting — Walking a
String· Inside aSplit - [x] pattern matching — Searching without splitting
- [x] substring search — Searching without splitting
- [x] char iteration — Walking a
String - [x] byte iteration — Walking a
String - [x] grapheme clusters — Four lengths, and which one the other system means · Meet the
char - [x] replacement — Replacing part of a string
- [x] case conversion — Comparing and sorting text
- [x] normalization — Comparing and sorting text · Normalization ↗ · glossary
Traits a string type implements (15)¶
- [x]
Display— Debug and Display - [x]
Debug— Debug and Display - [x]
ToString— Making aString - [x]
FromStr— Parsing out of a string - [x]
Deref<Target=str>—Stringvs&str - [x]
AsRef<str>— String parameters worth copying · glossary - [x]
Borrow<str>— When the UTF-8 invariant broke · glossary - [x]
Into<String>— String parameters worth copying ·FromandInto - [x]
From<String>—FromandInto· glossary - [x]
PartialEq— The comparison traits (stub) · Comparing and sorting text - [x]
Eq— The comparison traits (stub) - [x]
Ord— The comparison traits (stub) · Comparing and sorting text - [x]
Hash—HashSet· glossary - [x]
Clone—CopyvsClone - [x]
Copy(for&str) —CopyvsClone
Memory and layout (11)¶
- [x] heap allocation — Stack and heap · The anatomy of a
String - [x] stack allocation — Stack and heap
- [x] capacity — The anatomy of a
String - [x] length — The anatomy of a
String· Four lengths, and which one the other system means - [x] reallocation — The anatomy of a
String - [x] fat pointer —
stris unsized - [x] thin pointer —
stris unsized · glossary - [x] string interning — The third owned form:
Box<str>,Rc<str>,Arc<str>· glossary - [x] small-string optimization — The string crates · When
Stringis too slow (stub) · glossary - [x] null-terminated — Six kinds of string · glossary
- [x] non-null-terminated — Six kinds of string · glossary
Raw and FFI (10)¶
- [x]
std::ffi— Six kinds of string · Calling C — the call is free, the data is not - [x]
std::os::raw— Calling C — the call is free, the data is not · glossary - [x]
c_char— Calling C — the call is free, the data is not - [x] NUL byte — Six kinds of string
- [x] pointer casting — What
unsafeturns off · glossary - [x]
transmute— Why acharis 32 bits wide · glossary - [x] manual UTF-8 validation —
str::from_utf8· UTF-8 by hand ↗ · glossary - [x]
unsafe— Whatunsafeturns off - [x]
extern "C"— Calling C — the call is free, the data is not - [x] zero-copy — String slices · Inside a
Split· glossary
Iterators and views (14)¶
- [x]
Chars—str::chars - [x]
CharIndices—str::char_indices - [x]
Bytes—str::bytes - [x]
Lines—str::lines - [x]
Split—str::split· Inside aSplit - [x]
SplitWhitespace—str::split_whitespace - [x]
RSplit—str::rsplit - [x]
MatchIndices—str::match_indices - [x]
Matches—str::matches - [x]
RMatchIndices—str::rmatch_indices - [x]
EncodeUtf16—str::encode_utf16 - [x]
EscapeDebug—str::escape_debug - [x]
EscapeDefault—str::escape_default - [x]
EscapeUnicode—str::escape_unicode
Crates and ecosystem (15)¶
- [x]
unicode-segmentation— The string crates · Four lengths, and which one the other system means · glossary - [x]
unicode-normalization— The string crates · Comparing and sorting text · glossary - [x]
encoding_rs— The string crates · glossary - [x]
icu— The string crates · Comparing and sorting text · glossary - [x]
bstr— The string crates · Six kinds of string · glossary - [x]
byteorder— The string crates · Meet the byte · glossary - [x]
widestring— The string crates ·str::encode_utf16· glossary - [x]
utf16string— The string crates · glossary - [x]
compact_str— The string crates · glossary - [x]
smartstring— The string crates · glossary - [x]
smallstr— The string crates · glossary - [x]
tinystr— The string crates · glossary - [x]
arraystring— The string crates · glossary - [x]
flexstr— The string crates - [x]
cow-utils— The string crates · Replacing part of a string · glossary
Unicode concepts (12)¶
- [x] grapheme — Four lengths, and which one the other system means · Meet the
char - [x] code unit — Four lengths, and which one the other system means · glossary
- [x] code point — Why a
charis 32 bits wide · glossary - [x] scalar value — Why a
charis 32 bits wide · glossary - [x] combining character — Meet the
char· glossary - [x] normalization forms (NFC, NFD, NFKC, NFKD) — Comparing and sorting text · Normalization ↗ · glossary
- [x] case folding — Comparing and sorting text
- [x] canonical equivalence — The hard strings ↗ · Normalization ↗ · glossary
- [x] compatibility equivalence — The hard strings ↗ · Normalization ↗ · glossary
- [x] invisible characters — The hard strings ↗ · glossary
- [x] zero-width joiner — Meet the
char· glossary - [x] variation selector — Meet the
char· glossary
Error types (6)¶
- [x]
Utf8Error—str::from_utf8· glossary - [x]
FromUtf8Error—String::from_utf8· glossary - [x]
FromUtf16Error—String::from_utf16· glossary - [x]
ParseError— TheResultyou are reading is probably an alias · glossary - [x]
TryFromIntError—TryFromandTryInto - [x]
Infallible— TheResultyou are reading is probably an alias · The never type!
Common methods (33)¶
- [x]
len()—str::len· Four lengths, and which one the other system means - [x]
is_empty()—str::is_empty - [x]
contains()—str::contains - [x]
starts_with()—str::starts_with - [x]
ends_with()—str::ends_with - [x]
find()—str::find· Searching without splitting - [x]
rfind()—str::rfind - [x]
replace()—str::replace· Replacing part of a string - [x]
replacen()—str::replacen - [x]
to_lowercase()—str::to_lowercase· Comparing and sorting text - [x]
to_uppercase()—str::to_uppercase - [x]
repeat()—str::repeat - [x]
push()—String::push - [x]
push_str()—String::push_str - [x]
pop()—String::pop - [x]
insert()—String::insert - [x]
insert_str()—String::insert_str - [x]
remove()—String::remove - [x]
truncate()—String::truncate - [x]
clear()—String::clear - [x]
drain()—String::drain - [x]
split_off()—String::split_off - [x]
retain()—String::retain - [x]
reserve()—String::reserve - [x]
shrink_to_fit()—String::shrink_to_fit - [x]
with_capacity()—String::with_capacity - [x]
from_raw_parts()—String::from_raw_parts - [x]
make_ascii_lowercase()—str::make_ascii_lowercase - [x]
make_ascii_uppercase()—str::make_ascii_uppercase - [x]
is_ascii()—str::is_ascii - [x]
eq_ignore_ascii_case()—str::eq_ignore_ascii_case - [x]
escape_default()—str::escape_default - [x]
parse::<T>()—str::parse· Parsing out of a string
Pointer and safety (11)¶
- [x] dangling pointer — Use-after-free
- [x] null pointer — Null dereference · Nullable pointers
- [x] pointer alignment —
Allocator::shrink· glossary - [x] memory leak —
String::leak - [x] buffer overflow — Buffer overruns
- [x] out-of-bounds — Buffer overruns · String slices
- [x] invalid UTF-8 —
str::from_utf8 - [x] data race (via
unsafe) — Data races - [x]
MaybeUninit— Uninitialized reads ·Vec::spare_capacity_mut· glossary - [x]
NonNull— Nullable pointers · glossary - [x]
UnsafeCell— Interior mutability (stub) · glossary
Patterns and matching (9)¶
- [x] string literal —
&'static str· Raw strings, escapes and the literal prefixes - [x] raw string (
r"...",r#"..."#) — Raw strings, escapes and the literal prefixes - [x] byte string (
b"...") — Raw strings, escapes and the literal prefixes · RFC 69 — how Rust gotb'A' - [x] raw byte string (
br"...") — Raw strings, escapes and the literal prefixes - [x] multiline string — Raw strings, escapes and the literal prefixes
- [x] escape sequences (
\n,\t,\u{...},\x..) — Raw strings, escapes and the literal prefixes - [x] char literal — Meet the
char - [x] the
Patterntrait — Searching without splitting - [x]
Sealed— Searching without splitting · Sealed traits, C-SEALED ↗ · glossary
Smart pointers and wrappers (7)¶
- [x]
Rc<str>— The third owned form:Box<str>,Rc<str>,Arc<str>· glossary - [x]
Arc<str>— The third owned form:Box<str>,Rc<str>,Arc<str>· glossary - [x]
Mutex<String>— Lock poisoning - [x]
RwLock<String>—RwLockand atomics (stub) - [x]
RefCell<String>— Interior mutability (stub) - [x]
Cell<&str>— Interior mutability (stub) - [x]
Pin<Box<str>>— There is noMovetrait · glossary
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, &str→String four ways, String→&[u8], Vec<u8>→String |
Making a String · String vs &str |
Covered — from_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 closed — from_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 | Done — String 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 a…e 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
&strthat points into a localString— answered: theE0106-then-E0515pair, 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
Stringwith&strincorrectly — answered: measured on 1.98.0:s == "a","a" == s,&s == "a"ands.as_str() == "a"all compile; only comparing against acharor a&&strfails, asE0277. See Comparing and sorting text - [x] Forgetting
.as_str()where it is needed — answered:matchon a bareStringisE0308;String::as_strshows thematchthat needs it - [x]
+with two&str— the left operand must be an ownedString(Concatenating strings is the whole of this one) — answered, with a kata on that page that earns theE0369on purpose - [x] Assuming
.len()counts characters — it counts bytes — answered: Meet thecharand Four lengths - [x] Storing
&strin a struct without a lifetime parameter — answered: Pivot both ways, then earnE0106andE0515onStringvs&str, whose borrowedUserfield isE0106 - [x] Reaching for
Stringwhere&strwould do — an allocation nobody asked for — answered: String parameters worth copying, section 2: aStringparameter 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:
Stringif it is new data,&strif it borrows the input (with lifetimes) - Struct field:
String— rarely&str, and only with a lifetime parameter - Temporary:
&strfor 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'ssrc/liballoc/…paths are pre-2020 names — use the current ones) rustc --explainforE0106,E0506,E0716— ERRORS.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>.rsbuilds 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
.outartefact 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.outis amain()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.
-
Kata 1 has nothing to fix. The body already returns
"Hello, World!"and the test already passes; the// Make it workcomment 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. -
Kata 7 is
str::replacewith 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 usingreplace()". Then it becomes a real exercise infind, byte offsets andpush_str, and it pairs with Replacing part of a string, whose lesson is that a chain of replaces is not a substitution table. -
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 whatunicode-segmentationexists for and which the no-crates rule forbids. Fix it one of two ways — rename it tocount_charsand 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. -
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) -> &strreturns a slice borrowed from its input, which is lifetime elision doing exactly the thing Step 4 of the progression is about. -
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!" |
&str → String |
Already a kata — Let 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 kata — Greet 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 kata — Case 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 kata — The 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 kata — Find 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 kata — Find it without a regex engine, whose palindrome checker survives punctuation, case and multibyte letters |
| 7 | replace_all(s, from, to) |
find + byte offsets + push_str — once the "without replace()" constraint is added |
New — replace, 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 kata — Case 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 kata — Run-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 kata — Case 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 |
New — The 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 |
New — Every 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 | New — A regex engine with two operators, slice patterns on &[char]; the . that means half an é over bytes |
| 14 | word_ladder_length(...) |
BFS — see finding 4 | New — A 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 | New — Interleaving, 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 | New — Return 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 | New — A 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 kata — The 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$} |
New — Full 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 |
New — Edit 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, amain(), 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.