Lesson Details

We now have a table set up that we can insert PokemonCsvRows into, but the Iterator returned by deserialize() in src/main.rs only gives us PokemonCsvs.

Our goal is to get this line of code working in src/main.rs.

let pokemon_row: PokemonTableRow = record.into();

This line of code specifies that the variable pokemon_row is a PokemonTableRow, which into() can then use to convert the record, a PokemonCsv, into a PokemonTableRow.

into is not magic: we, the standard library, or someone else have to define how it works for these two types.

The into function comes from the Into trait so it would be reasonable to think that we could implement the Into trait for impl Into<PokemonTableRow> for PokemonCsv. We definitely could do this and it would work but there's a more idiomatic trait to implement: From.

From and Into do essentially the same thing. The reason both exist is partially historical and partially to support edge cases. There are (or have been in the past) types we could not implement From for. We talked previously about the orphan rules (that is, implementing a trait for types that aren't owned by our current crate), and this is one of the ways that From used to be more restrictive than Into. This is OK. It means Rust used to be more strict and improvements were made and it got easier to use.

As a result of this, any type that implements From also gets an Into implementation for free. (It does not work the other way around).

So if we implement impl From<PokemonCsv> for PokemonTableRow, then we get pokemon_csv.into() for free.

We'll implement From in src/db.rs since that's where PokemonTableRow is. The From trait requires us to implement the from function which takes a PokemonCsv and returns Self, a PokemonTableRow.

Since we're using PokemonCsv we'll bring it into scope at the top of the file.

use crate::pokemon_csv::PokemonCsv;

We'll destructure the PokemonCsv in the from argument. This brings each of the names for each of the fields from PokemonCsv into scope. We use _ in the value place of the destrucuring for fields that we don't use.

impl From<PokemonCsv> for PokemonTableRow {
    fn from(
        PokemonCsv {
            name,
            pokedex_id,
            abilities: _,
            typing: _,
            hp,
            attack,
            defense,
            special_attack,
            special_defense,
            speed,
            height,
            weight,
            generation,
            female_rate,
            genderless,
            is_legendary_or_mythical,
            is_default,
            forms_switchable,
            base_experience,
            capture_rate,
            egg_groups: _,
            base_happiness,
            evolves_from: _,
            primary_color,
            number_pokemon_with_typing,
            normal_attack_effectiveness,
            fire_attack_effectiveness,
            water_attack_effectiveness,
            electric_attack_effectiveness,
            grass_attack_effectiveness,
            ice_attack_effectiveness,
            fighting_attack_effectiveness,
            poison_attack_effectiveness,
            ground_attack_effectiveness,
            fly_attack_effectiveness,
            psychic_attack_effectiveness,
            bug_attack_effectiveness,
            rock_attack_effectiveness,
            ghost_attack_effectiveness,
            dragon_attack_effectiveness,
            dark_attack_effectiveness,
            steel_attack_effectiveness,
            fairy_attack_effectiveness,
        }: PokemonCsv,
    ) -> Self {
        let id = PokemonId::new();
        let slug = name.to_kebab_case();
        PokemonTableRow {
            id,
            slug,
            name,
            pokedex_id,
            hp: hp.into(),
            attack: attack.into(),
            defense: defense.into(),
            special_attack: special_attack.into(),
            special_defense: special_defense.into(),
            speed: speed.into(),
            height,
            weight,
            generation: generation.into(),
            female_rate,
            genderless,
            legendary_or_mythical: is_legendary_or_mythical,
            is_default,
            forms_switchable,
            base_experience,
            capture_rate: capture_rate.into(),
            base_happiness: base_happiness.into(),
            primary_color,
            number_pokemon_with_typing,
            normal_attack_effectiveness,
            fire_attack_effectiveness,
            water_attack_effectiveness,
            electric_attack_effectiveness,
            grass_attack_effectiveness,
            ice_attack_effectiveness,
            fighting_attack_effectiveness,
            poison_attack_effectiveness,
            ground_attack_effectiveness,
            fly_attack_effectiveness,
            psychic_attack_effectiveness,
            bug_attack_effectiveness,
            rock_attack_effectiveness,
            ghost_attack_effectiveness,
            dragon_attack_effectiveness,
            dark_attack_effectiveness,
            steel_attack_effectiveness,
            fairy_attack_effectiveness,
        }
    }
}

We can construct a new PokemonId using the new method we've defined for it. We'll use that as the id field to our PokemonTableRow.

let id = PokemonId::new();

To convert our pokemon names into slugs, we can install the inflector crate. When we do, cargo tells us that Inflector was added instead of inflector. This is because the Inflector package was published originally with a capital I, but also that crate names are case-insensitive, so inflector and Inflector are the same crate anyway, always. So cargo gives us the one we meant.

❯ cargo add inflector -p upload-pokemon-data
WARN: Added `Inflector` instead of `inflector`
    Updating '<https://github.com/rust-lang/crates.io-index>' index
      Adding Inflector v0.11.4 to dependencies

We'll need to bring the Inflector trait into scope, which is an extension trait that adds additional methods to strings.

use inflector::Inflector;

One of those methods is to_kebab_case, which is good enough for us to slugify our names.

let slug = name.to_kebab_case();

Finally we construct a PokemonTableRow. Since we have names in scope that are the same as the names in the PokemonTableRow type, we can write them once instead of writing the field name and the value. The one exception is the places that we're converting u8s to u16s using into.

Rust knows how to convert the u8s into u16s because each struct's field is typed as such and the relevant traits are implemented for many standard library primitive types.

Our main function in src/main.rs can now change to use into. We'll also change the println to show pokemon_row.

fn main() -> Result<(), csv::Error> {
    let mut rdr = csv::Reader::from_path(
        "./crates/upload-pokemon-data/pokemon.csv",
    )?;
    for result in rdr.deserialize() {
        let record: PokemonCsv = result?;
        let pokemon_row: PokemonTableRow = record.into();
        dbg!(pokemon_row);
    }
    Ok(())
}

and a cargo run should show us a bunch of PokemonTableRows with the ids and slugs we've generated.

[crates/upload-pokemon-data/src/main.rs:13] pokemon_row = PokemonTableRow {
    id: PokemonId(
        "2VYh2bIMdAgd4oIPsdBFSAXmc35",
    ),
    name: "Calyrex Shadow Rider",
    slug: "calyrex-shadow-rider",
    pokedex_id: 898,
    hp: 100,
    attack: 85,
    defense: 80,
    special_attack: 165,
    special_defense: 100,
    speed: 150,
    height: 24,
    weight: 536,
    generation: 8,
    female_rate: None,
    genderless: true,
    legendary_or_mythical: true,
    is_default: false,
    forms_switchable: true,
    base_experience: 340,
    capture_rate: 3,
    base_happiness: 100,
    primary_color: "green",
    number_pokemon_with_typing: 4.0,
    normal_attack_effectiveness: 0.0,
    fire_attack_effectiveness: 1.0,
    water_attack_effectiveness: 1.0,
    electric_attack_effectiveness: 1.0,
    grass_attack_effectiveness: 1.0,
    ice_attack_effectiveness: 1.0,
    fighting_attack_effectiveness: 0.0,
    poison_attack_effectiveness: 0.5,
    ground_attack_effectiveness: 1.0,
    fly_attack_effectiveness: 1.0,
    psychic_attack_effectiveness: 0.5,
    bug_attack_effectiveness: 1.0,
    rock_attack_effectiveness: 1.0,
    ghost_attack_effectiveness: 4.0,
    dragon_attack_effectiveness: 1.0,
    dark_attack_effectiveness: 4.0,
    steel_attack_effectiveness: 1.0,
    fairy_attack_effectiveness: 1.0,
}