We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Collections, Text and First Iterators step 18 of 21
Cow in API design: impl Into<Cow<str>>
Use Cow to let a message hold either borrowed or owned text. Then render placeholders while returning borrowed text when the template needs no parsing.
Keep these public functions:
pub fn render(template: &str, vars: Vec<(String, String)>) -> Cow<'_, str>
pub fn build(inputs: Vec<String>) -> Vec<(String, bool)>
Define Message<'a> with a text: Cow<'a, str> field and a constructor
Message::new(text: impl Into<Cow<'a, str>>) -> Self.
Render placeholders
If template contains no opening brace, return Cow::Borrowed(template)
without allocating an output buffer. Otherwise return Cow::Owned, even when
the final text is unchanged or empty.
Parse from left to right using these rules:
- Copy text before the next
{. - Find the first
}after that opening brace. Everything between them is the key. Keys are case-sensitive and may be empty or contain whitespace, Unicode or another opening brace. Do not trim them. - Replace a known key with its value. If vars contains duplicate keys, use the first matching pair. Preserve unknown placeholders, including braces.
- Continue after the closing brace. Do not parse replacement values again.
- If an opening brace has no closing brace, preserve the rest unchanged.
There is no nesting or escape syntax. A closing brace outside a placeholder
is ordinary text. For example, {{x}} treats {x as the key and leaves the
final } as ordinary text. A backslash does not escape a brace.
With vars equal to [("name", "Ada")], "Hi {name}!" becomes owned
"Hi Ada!", "{missing}" stays owned "{missing}", and "Hi!" is borrowed.
An empty template is borrowed. The harness checks both the text and variant.
Build messages
Process inputs in order. For an empty string, construct a message from the
literal "(empty)". For any nonempty string s, construct one from the owned
String produced by format!("<{s}>"). Whitespace-only strings are nonempty.
For each message, record whether its text is Cow::Borrowed, then convert the
text into a String and return (text, was_borrowed). Empty inputs therefore
produce ("(empty)", true), and "x" produces ("<x>", false).
An empty input list produces no pairs.
The boolean describes the intermediate Cow. The returned pair always contains an owned String: converting the borrowed literal with into_owned copies it. This does not make the entire build path allocation-free.
What the lifetime means
Cow<'a, str> is either a borrowed &'a str or an owned String. A struct field
using Cow needs a lifetime argument, so the starter's Cow<str> field fails
with E0106. Use Message<'a> and impl<'a> Message<'a> to name the lifetime.
The lifetime limits any text the message borrows. It does not mean every
message borrows, nor does it force every caller to spell out a named lifetime.
Functions can often use Message<'_>, and an owned or static-string message
can be represented as Message<'static>.
Cow can be stored in collections or sent between threads when its lifetimes and trait bounds permit. If an API always needs independent ownership, String may be simpler. Cow is useful when preserving a caller's borrow is valuable.
Accept either ownership form
The starter's String-only constructor rejects the literal in build with
E0308. Accepting impl Into<Cow<'a, str>> lets the constructor call .into():
a string slice becomes Borrowed, a String becomes Owned, and an existing Cow
can be passed through. Moving a String into Cow transfers its buffer rather
than cloning the contents.
This complete example demonstrates the same constructor pattern:
use std::borrow::Cow;
struct Label<'a> { text: Cow<'a, str> }
impl<'a> Label<'a> {
fn new(text: impl Into<Cow<'a, str>>) -> Self {
Self { text: text.into() }
}
}
fn main() {
let source = String::from("draft");
let borrowed = Label::new(source.as_str());
assert!(matches!(borrowed.text, Cow::Borrowed(_)));
assert_eq!(borrowed.text, "draft");
let owned = Label::new(String::from("saved"));
assert!(matches!(owned.text, Cow::Owned(_)));
assert_eq!(owned.text.into_owned(), "saved");
}
AsRef<str> offers a shared string view. It is useful when the operation only needs to read text, but it does not promise that the function will never store its argument. Into<Cow> supplies the ownership conversion this constructor needs; AsRef alone does not let it extract an owned String buffer.
Avoid unnecessary copies
into_owned() consumes a Cow, moving out its String or copying borrowed
text into one. to_owned() on a Cow clones the Cow itself: a borrowed Cow
stays borrowed, while an owned Cow's contents are cloned. Choose based on
whether you need another Cow or an independent String.
For render, the no-opening-brace check is the ownership rule of this exercise. It is not the only possible design. Another API could scan for actual changes and allocate only when needed; that would have a different variant contract.
str::find returns byte indices. Slicing around matched ASCII braces is safe
for UTF-8 because those indices and the positions immediately after the
braces are character boundaries. Avoid treating arbitrary byte indices as
character positions.
Submissions must compile, pass tests and pass Clippy. The tests check output text and ownership flags, not exact allocation counts or every API signature.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.