We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 19 of 24
Blanket implementations: shared behavior and overlap
Implement Describe once for every sized type that implements Debug.
Replace the starter's string-only implementation and leave run unchanged.
pub trait Describe {
fn describe(&self) -> String;
}
pub fn run(nums: Vec<i64>, label: String, flag: bool) -> Vec<String>
Use impl<T: Debug> Describe for T. The method returns the diagnostic name
from std::any::type_name::<T>(), followed by ": " and the value's Debug
representation. format!("{}: {:?}", std::any::type_name::<T>(), self)
expresses that formatting.
run describes five values in order: nums, label, flag, the constant
42u8, and the fixed string literal "hi". That last literal does not
change when label changes.
For [1, 2, 3], "hi", and true, the expected strings are:
alloc::vec::Vec<i64>: [1, 2, 3]
alloc::string::String: "hi"
bool: true
u8: 42
&str: "hi"
String debug formatting includes surrounding quotes and escapes characters
such as quotes, tabs and newlines. Use Debug rather than building these
escapes yourself. The numeric inputs are signed 64-bit integers.
type_name is intended
for diagnostics. Its spelling is not a stable serialization format or a
guaranteed unique type identifier. The output above matches this exercise's
toolchain; do not use these strings as persistent type IDs in an application.
One implementation covers many types
A blanket implementation uses a type parameter to cover a family of types.
The bound decides which types belong to that family. Here, T: Debug
permits vectors, strings, booleans and many other types to share the same
method body.
T also has an implicit Sized bound. A str value is unsized, but a
reference &str is sized. With this implementation, calling
"hi".describe() selects T = &str; its &self receiver borrows that
reference. That is why the final output names &str, not str.
Adding ?Sized would expand the implementation to unsized types and can
change method selection and the reported name. Keep the requested sized
bound for this exercise.
Fix the starter's missing methods
The starter only implements Describe for String. Calls on the vector,
boolean, integer and string slice therefore produce missing-method errors
such as E0599. Its Debug import is also unused until you add the blanket
implementation.
Once you add impl<T: Debug> Describe for T, remove the old
impl Describe for String. Keeping both produces E0119 because String
already implements Debug: both implementations would apply to the same
type. No hypothetical future implementation is needed to explain this
particular conflict.
Stable Rust does not choose the string-specific implementation as an override here. The implementation sets overlap, so the compiler rejects them.
A type outside the bound can have its own implementation
“Blanket” does not mean that every other implementation is forbidden.
This complete example gives a local type its own behavior because it does
not implement Debug:
use std::fmt::Debug;
trait Describe {
fn describe(&self) -> String;
}
impl<T: Debug> Describe for T {
fn describe(&self) -> String {
format!("{}: {:?}", std::any::type_name::<T>(), self)
}
}
struct Secret(String);
impl Describe for Secret {
fn describe(&self) -> String {
format!("secret with {} bytes", self.0.len())
}
}
fn main() {
assert_eq!(true.describe(), "bool: true");
assert_eq!(Secret("abc".to_owned()).describe(), "secret with 3 bytes");
}
Adding #[derive(Debug)] to Secret would make these implementations
overlap. A wrapper can provide a separate type for custom behavior, but
only if its implementations still satisfy the overlap rules. This is an
API-design choice, not an automatic escape from coherence checking.
Local traits and foreign traits
Describe belongs to this crate, so the crate may implement it for types
from the standard library. This is separate from the overlap check: owning
the trait permits the implementation under the orphan rules, but does not
permit two conflicting implementations.
A local trait can also have a generic implementation for Vec<T>; the
vector being foreign does not itself make that invalid. Conversely, a
generic implementation of a foreign trait for a local wrapper can be valid.
The rule is not “never implement a foreign trait generically.”
An implementation of the foreign Display trait for arbitrary T lacks
the required local type and is rejected. Foreign-trait implementations have
additional restrictions on where uncovered type parameters may occur. See
the coherence and orphan rules
for the full conditions.
Choose blanket bounds to match the behavior you intend to provide. Broad implementations can prevent downstream custom implementations for covered types, so consider that constraint before publishing the API. A broad implementation is not inherently useless; shared behavior may be exactly what the trait is designed to offer.
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.