We'll want to dbg!(a_pokemon_table_row)
at some point in our work, so we can derive Debug
in for the PokemonTableRow
type in db.rs
.
#[derive(Debug)]
pub struct PokemonTableRow {
This works for most of the field types we have in PokemonTableRow
, but not for Ksuid
because Ksuid
doesn't implement Debug
itself, and when we derive(Debug)
we're basically delegating to the fields to tell us how to format themselves. So hp
formats like a u16
, etc.
The error looks like this:
error[E0277]: `Ksuid` doesn't implement `Debug`
--> crates/upload-pokemon-data/src/db.rs:5:5
|
3 | #[derive(Debug)]
| ----- in this derive macro expansion
4 | pub struct PokemonTableRow {
5 | pub id: Ksuid,
| ^^^^^^^^^^^^^ `Ksuid` cannot be formatted using `{:?}` because it doesn't implement `Debug`
|
= help: the trait `Debug` is not implemented for `Ksuid`
= note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)
We can't derive(Debug)
for Ksuid
because there's no place to put the derive
macro.
We can't even implement Debug
ourselves.
If we try to implement Debug
for Ksuid
with the following generally valid code that is invalid for this case.
impl fmt::Debug for Ksuid {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.debug_struct("Ksuid")
.field("id", &self.to_base62())
.finish()
}
}
We get the following error.
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
--> crates/upload-pokemon-data/src/db.rs:53:1
|
53 | impl fmt::Debug for Ksuid {
| ^^^^^^^^^^^^^^^^^^^^-----
| | |
| | `Ksuid` 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
For more information about this error, try `rustc --explain E0117`.
The reason we can't implement Debug
for Ksuid
is because you can't implement Traits that aren't yours for types that also aren't yours. Debug
comes from the Rust standard library, not our crate, and Ksuid
comes from the ksuid crate, not our crate.
This restriction exists for a reason. Let's say we took the Debug
trait and the Ksuid
type and we implemented them in two separate crates on crates.io: crate-a
and crate-b
.
That means we'd have impl Debug for Ksuid
in crate-a
and impl Debug from Ksuid
in crate-b
. As a result, these two crates would become incompatible because there is no way for Rust to choose between the two implementations, so installing one would prevent you from installing the other due to Rust being unable to pick an implementation to use.
Implementations of traits for types that you don't own is often referred to alongside the "orphan rule" in Rust, or can be referred to as "orphan instances" in other languages.
There is good news though! If our crate owns either the Trait or the type, then we're free to use them as we wish.
As a result of this we get two interesting applications: Extension traits and newtypes.
Extension traits are a way to extend a type with additional functions. This isn't the focus of this lesson so I'll instead point you to an example of an extension trait: The Itertools
trait in the itertools crate.
The approach we'll be taking for the Ksuid
type is the newtype pattern. Some languages have a special keyword for "newtype". Rust does not, so we'll be using a tuple struct.
Instead of using Ksuid
in our PokemonTableRow
we'll create a new tuple struct called PokemonId
with a single element of type Ksuid
.
pub struct PokemonId(Ksuid);
We can then use this new type as the type of our id.
pub struct PokemonTableRow {
pub id: PokemonId,
...
}
PokemonId
in this case acts as a wrapper around the Ksuid
so if we had a Ksuid
already created that was in a variable named some_ksuid
. We could construct a new PokemonId
by wrapping some_ksuid
.
let pokemon_id = PokemonId(some_ksuid);
Since we own this newtype wrapper PokemonId
we can implement Debug
for it now... and that Debug
implementation can access the underlying Ksuid
as the 0th index of the tuple.
impl fmt::Debug for PokemonId {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.debug_tuple("PokemonId")
.field(&self.0.to_base62())
.finish()
}
}
It's often the case that we will refer to different types of items in Rust with the same name. For example the Debug
we use in derive(Debug)
is a derive macro while the Debug
we use in the trait implementation for PokemonId
is a trait.
This is possible because different types of items, in this case dervice macros and traits, are in different namespaces. So when in a position that requires a trait, Rust will look in the traits namespace for Debug
, while when we're in a derive
call, Rust will look in the derive macros namespace, and the two names don't actually collide.
To be clear in our program, I'm going to use fmt::Debug
for the trait since we have to bring it into scope anyway. This requires bringing fmt
into scope.
use std::fmt
The Debug
trait requires that we implement the fmt
function. The fmt
function type is defined by the trait, so we're basically just copying it here because we have to. It takes a shared reference to self
, which in our case is a PokemonId
, and an exclusive reference to a Formatter
because we will be mutating it by changing options and such.
The <'_>
is an anonymous lifetime. We're only using what Rust tells us to use here so I won't get deeper into what an anonymous lifetime is yet.
The final part of the type signature is fmt::Result
which is a type alias for a Result<(), fmt::Error>
. These kinds of type aliases are fairly common as it means you don't need to think about, choose, and write the error type when working with common APIs.
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result
An fmt::Formatter
gives us access to a few helper functions for building up good debug output so that we don't need to write and match symbols like (
. One of those helper functions is debug_tuple
which we can pass a string to identify the tuple. This string is arbitrary but is usually the name of the type.
debug_tuple
uses the builder pattern to allow us to add fields to the tuple using the .field
function, and .finish()
finalizes the builder.
f.debug_tuple("PokemonId")
.field(&self.0.to_base62())
.finish()
A PokemonId
has a Ksuid
at the 0th index in the tuple. The Ksuid
has a useful function to_base62
which will output an alphanumeric version of the id.
To print out a PokemonId
we first need to create one! To make that easier, we'll impl a new
method on the PokemonId
type.
Ksuid
has a generate
method that we can wrap with PokemonId
.
impl PokemonId {
pub fn new() -> Self {
PokemonId(Ksuid::generate())
}
}
So now somewhere in main.rs
in the main
function, we can add a dbg!
macro to print out the debug representation of a new PokemonId
.
fn main() -> Result<(), csv::Error> {
...
dbg!(PokemonId::new());
...
}
When we use cargo run
, right at the end the program outputs:
[crates/upload-pokemon-data/src/main.rs:15] PokemonId::new() = PokemonId(
"2VYazoZJ8rCaaNKUWZAd9Twj47X",
)