We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 5 of 26
Deriving traits, and when derive gets the bounds wrong
Report the default value of named fields on a Settings struct.
pub fn field_defaults(names: Vec<String>) -> Vec<String>
For each name, return retries as a plain number, verbose as true/false,
label with {:?} (so an empty string shows up as ""), and "?" for any
name that is not a field.
The starter compiles and passes every test, and it still fails. That is
the lesson. It ships a hand-written impl Default for Settings whose body is,
line for line, exactly what #[derive(Default)] would have generated. clippy
notices:
error: this `impl` can be derived
Your job is to delete the hand-written impl and put Default in the derive
list. Roughly ten lines of code disappear.
What derive actually is
#[derive(Debug, Clone, PartialEq)] is a macro that reads your type
definition and writes trait implementations for it. Nothing more. The code it
writes is ordinary Rust that you could have typed yourself, and — this is the
part that matters — you own what it generates. It is not a compiler
primitive you can wave at, it is generated source with consequences.
The std traits you can derive: Debug, Clone, Copy, PartialEq, Eq,
PartialOrd, Ord, Hash, Default. That is the whole list. Notably
Display is deliberately not derivable — there is no single obviously
correct way to show a type to a human, so std makes you decide.
Derived Default gives each field that field’s default: 0 for integers,
false for bool, "" for String, None for Option<T>, an empty Vec.
On an enum you must nominate a variant with #[default].
Where the abstraction leaks: derive’s bounds
Here is the thing almost nobody tells beginners, and the source of a whole
family of baffling E0277s later on.
Derive adds a bound for every type parameter, whether or not it is needed.
#[derive(Clone)]
struct Wrap<T>(std::rc::Rc<T>);
The generated impl is impl<T: Clone> Clone for Wrap<T>. But Rc<T> is
Clone for every T — cloning an Rc bumps a refcount and never touches
the T at all. So Wrap<NotClonable> could be Clone, and thanks to
derive it is not. Writing the impl by hand fixes it:
impl<T> Clone for Wrap<T> {
fn clone(&self) -> Self { Wrap(self.0.clone()) }
}
Same story for PartialEq on a PhantomData<T> field, Debug on a type
whose T is only ever used behind a reference-counted pointer, and so on.
When a generic type mysteriously “does not implement Clone“ and you can see
no reason why, this is usually the reason.
It is also why the derivable_impls lint that grades this problem
deliberately switches itself off for generic types: on a generic type the
hand-written impl and the derived one may genuinely differ, so clippy will
not tell you they are the same. On a plain concrete struct like Settings
there is no such escape, and the lint is right.
Two more lints in this family
expl_impl_clone_on_copy — if a type is Copy, writing Clone by hand is
suspicious, because the only correct Clone for a Copy type is *self.
derive_partial_eq_without_eq — a type that could be Eq but only derives
PartialEq is usually an oversight. (Only “usually”: floats are the famous
exception, and they get an item of their own later in this track.)
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.