We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 18 of 20
Method receivers and the consuming builder
Build one configuration for each command list. Some methods return an updated configuration by value; a summary method borrows it so you can continue adding settings afterwards.
pub fn build_configs(specs: Vec<Vec<String>>) -> Vec<String>
Configuration and commands
Use Config { retries: u32, tags: Vec<String> } with these methods:
| Method | Behavior |
|---|---|
new() -> Config |
Start with zero retries and no tags. |
retries(mut self, n: u32) -> Self |
Replace the retry count and return the config. |
tag(mut self, t: String) -> Self |
Append the tag and return the config. |
to_summary(&self) -> String |
Return r<count> t<number-of-tags> without consuming the config. |
build(self) -> String |
Return retries=<count>;tags=<comma-joined-tags>, consuming the config. |
Process each spec independently and preserve spec order. Within a spec, process commands in order using these exact, case-sensitive rules:
summaryrecords a summary of the current state. Repeated summaries each produce an entry.- A command starting with
retriesparses its entire suffix asu32. On failure, set retries to zero. This replaces the previous count. - A command starting with
tagappends its entire suffix unchanged. Empty tags, repeated tags, spaces, commas and Unicode are allowed. - Ignore every other command. Do not trim commands or their suffixes.
Retry parsing accepts ASCII decimal digits with an optional leading +
and leading zeros, in 0..=4294967295. Negative signs, whitespace,
underscores, empty suffixes and overflow fail. Thus retries +003 sets
three retries, retries 3 sets zero, and retries without a space is ignored.
For each spec, join its summaries with |, put them in brackets, append a
space, then append the built config. Preserve tags in insertion order and
join them with commas, without escaping their contents.
For example, ["retries 3", "tag a", "summary", "tag b"] produces
"[r3 t1] retries=3;tags=a,b". An empty spec produces
"[] retries=0;tags=". An empty outer list produces no output elements.
Implement the required consuming methods without cloning the config or
its owned fields. The harness checks output; it does not enforce every
method signature or detect all possible cloning implementations.
Read the receiver as an ownership decision
| Common receiver | What the method receives |
|---|---|
&self |
A shared borrow. |
&mut self |
An exclusive borrow that permits mutation. |
self |
The value by value. |
mut self |
The value by value, in a mutable local binding. |
These are common forms, not the complete receiver syntax: methods can
also use receivers such as self: Box<Self>. A shared receiver does not
guarantee an unchanged value, because interior mutability can permit changes.
A returned reference can also keep a borrow active after a call.
For this non-Copy Config, passing self moves the value into the method.
A Copy type can instead be copied into a by-value call. In mut self,
mut changes the local binding; it does not turn the receiver into a borrow.
Retain the returned builder
The starter calls cfg.retries(...) and cfg.tag(...) as statements.
Each call moves cfg in, returns an updated value, then discards that value.
A later use of the old binding produces E0382. Make the binding mutable and
reassign the returned config after each step.
The starter also gives to_summary a by-value receiver. Change it to
&self so recording a summary leaves the config available for later commands.
Clippy's wrong_self_convention can flag a consuming to_* method on a
non-Copy type, but move errors may prevent that lint from being reached.
Here is a complete consuming chain using a smaller builder:
struct Label(String);
impl Label {
fn new() -> Self { Self(String::new()) }
fn append(mut self, text: &str) -> Self {
self.0.push_str(text);
self
}
fn build(self) -> String { self.0 }
}
fn main() {
let text = Label::new().append("hello").append(" world").build();
assert_eq!(text, "hello world");
}
Each step transfers ownership and returns it. This permits chaining without cloning. It does not itself guarantee all required settings were supplied; that would need validation or types representing different builder states. Here, the default configuration is already valid.
A mutable builder is another valid design
A method can instead take &mut self and return &mut Self. With an owned
variable, finish the mutable borrow before calling a consuming build:
struct Label(String);
impl Label {
fn new() -> Self { Self(String::new()) }
fn append(&mut self, text: &str) -> &mut Self {
self.0.push_str(text);
self
}
fn render(&self) -> String { format!("[{}]", self.0) }
fn build(self) -> String { self.0 }
}
fn main() {
let mut label = Label::new();
label.append("hello").append(" world");
assert_eq!(label.build(), "hello world");
// A temporary chain works when the final method borrows and returns owned data.
let rendered = Label::new().append("temporary").render();
assert_eq!(rendered, "[temporary]");
}
Calling a non-Copy builder's build(self) directly on the &mut Self
returned by a setter would attempt to move through a borrow. The example
avoids that by ending the statement before consuming the owned variable.
Use the consuming signatures requested for this exercise.
Method prefixes are useful conventions, not cost guarantees. as_* often
exposes a borrowed view, to_* often creates an owned representation, and
into_* conventionally consumes its receiver. The implementation determines
allocations and other work. Remove unnecessary mut bindings if the compiler
reports unused_mut. Submissions must compile, pass tests and pass Clippy.
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.