Log in to access Rust Adventure videos!

Lesson Details

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.

#[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:7:5
  |
7 |     pub id: Ksuid,
  |     ^^^^^^^^^^^^^ `Ksuid` cannot be formatted using `{:?}` because it doesn't implement `Debug`
  |
  = help: the trait `Debug` is not implemented for `Ksuid`
  = note: required because of the requirements on the impl of `Debug` for `&Ksuid`
  = note: required for the cast to the object type `dyn Debug`
  = note: this error originates in a derive macro (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 arbitrary types
  --> crates/upload-pokemon-data/src/db.rs:54:1
   |
54 | 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

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 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.

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());
    ...
}

which outputs

[crates/upload-pokemon-data/src/main.rs:14] PokemonId::new() = PokemonId(
    "1xVsWc4KAGTRJhYCH7Vsx4SOnzL",
)