title | timestamp | author | published | description | tags | |||||
---|---|---|---|---|---|---|---|---|---|---|
Add the Display trait to a vector of strings in Rust |
2023-12-21 01:50:01 -0800 |
szabgab |
true |
How to implement a trait for a built-in type or for a type-alias for a built-in type? |
|
Let's say we have a vector of strings, how can we print them easily?
let animals: Vec<String> = vec![String::from("snake"), String::from("camel"), String::from("crab")];
Trying to use the simplest way won't work:
println!("{}", animals);
We get the following error:
error[E0277]: `Vec<String>` doesn't implement `std::fmt::Display`
--> src/main.rs:24:20
|
24 | println!("{}", animals);
| ^^^^^^^ `Vec<String>` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Vec<String>`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
further referring us to E0277
println!("{:?}", animals);
will yield
["snake", "camel", "crab"]
and
println!("{:#?}", animals);
will yield:
[
"snake",
"camel",
"crab",
]
Those are actually not bad, but I wanted to show them in a different way.
Primarily as an experiment to see how to implement Display in general for a vector.
The implementation should look something like this:
impl fmt::Display for Vec<String> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "Something comes here")
}
}
However, this will give us a compiler error:
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
--> src/main.rs:23:1
|
23 | impl fmt::Display for Vec<String> {
| ^^^^^^^^^^^^^^^^^^^^^^-----------
| | |
| | `Vec` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
Referencing us to E0117.
We cannot as we can only implement traits to our own data types.
What about creating an alias for this type? Would crating a type-alias work?
type Words = Vec<String>;
impl fmt::Display for Words {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "Something comes here")
}
}
let animals: Words = vec![String::from("snake"), String::from("camel"), String::from("crab")];
println!("{:?}", animals);
We can define the type-alias and we can even use it in the declaration of variables, but we cannot implement the Display trait on a type-alias to a standard type. Type-aliases are just that. Aliases. They are not separate types.
A working solution is to create a one-element tuple-based struct. Because it is a totally new type we can implement the Display trait for that.
struct Words(Vec<String>);
impl fmt::Display for Words {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.join(","))
}
}
This is then how to use it:
let animals: Words = Words(vec![String::from("snake"), String::from("camel"), String::from("crab")]);
println!("{}", animals);
It will print:
snake,camel,crab
The drawback is that this means we need to reference to the 0th element in the tuple so we must write animals.0
in order to access the real vector:
println!("{}", animals.0[0]);
println!("{}", animals.0[1]);
to print:
snake
camel
I don't think I like this.
{% include file="examples/display-vector-of-strings/src/main.rs" %}
We could not implement Display for a vector of strings in a nice way. If we frequently need to print out a whole vector of strings other than for debugging, then we might want to write a separate function or a macro for that.