We have a csv with Pokemon data in it that we'll be using to build up our database schema, so the first step is to get the csv file into Rust.
Download the csv from GitHub and put it in crates/upload-pokemon-data/pokemon.csv
.
We're going to use the csv crate, so using cargo-edit we can specify which package we want to add csv
to using the -p
flag.
cargo add -p upload-pokemon-data csv
then we can replace our main
function with an implementation that reads the csv in and processes it.
fn main() -> Result<(), csv::Error> {
let mut rdr = csv::Reader::from_path(
"./crates/upload-pokemon-data/pokemon.csv",
)?;
for result in rdr.records() {
let record = result?;
println!("{:?}", record);
}
Ok(())
}
We'll start off by changing the type signature of main
to return a Result<(), csv::Error>
. The only errors we'll encounter in this lesson are csv::Error
s, and the main function has to return unit, ()
, if successful.
Adding the Result type to our main
function allows us to use the ?
on Result
types returned from the csv crate functions to handle any errors.
For example, csv::Reader::from_path
accepts a filepath to the csv file we want to read.
let mut rdr = csv::Reader::from_path(
"./crates/upload-pokemon-data/pokemon.csv",
)?;
The from_path
method returns a Result<Reader<File>, csv::Error>
. Since the main
function return type is the same error type, we can use ?
on the Result<Reader<File>, csv::Error>
to turn it into a Reader<File>
. If the value was an error, it will get returned to the main function immediately and the program will end.
Which makes rdr
a Reader<File>
. We let rustc know we're going to need exclusive access to the reader so we can mutate it by using the mut
keyword. Rust will use this information to make sure we aren't mutating the Reader<File>
from multiple locations at once, which would result in confusing and hard-to-debug bugs.
The Reader<File>
type is the csv::Reader
struct from the csv crate. This type implements a different, similarly named trait called Read
from the standard library: std::io::Read
. Types that implement Read
are usually called "readers", so this is appropriate.
A reader allows us to read bytes from... somewhere. In this case it's a file, but it could also be a tcp socket or something even more different. readers allow us to build complex functionality on top of them in case we need to perform actions on truly massive files that don't live in memory, or files that don't fully exist yet.
Our usage is pretty pedestrian by comparison, we could easily read the entire csv into a string without issue considering how small our csv is.
Here the csv::Reader
includes a records
function that makes it so we can loop over each row nicely though, so we'll use that.
for result in rdr.records() {
let record = result?;
println!("{:?}", record);
}
records
returns a type that implements the Iterator
trait, so we can use it in a for loop to access each row.
Because we haven't specified any types to deserialize the csv rows into, the csv crate will hand us a generic StringRecord
struct with the values for each row. Here's an example:
StringRecord(["Bulbasaur", "1", "Overgrow, Chlorophyll", "Grass, Poison", "45", "49", "49", "65", "65", "45", "7", "69", "1", "0.125", "False", "False", "True", "False", "64", "45", "Monster, Plant", "70", "", "green", "15.0", "1.0", "2.0", "0.5", "0.5", "0.25", "2.0", "0.5", "1.0", "1.0", "2.0", "2.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "0.5"])
It's possible that getting this StringRecord
could fail, so the result
variable is of type Result<StringRecord, csv::Error>
, which we can again handle with ?
. This effectively unwraps the Result
for us, or returns from main with the error if one exists.
So record
is a StringRecord
type and we print that out using the println!
macro.
The println!
macro is a bit like a fancy console.log
in JavaScript. println!
accepts a format string, and variables to format into that string.
The format string we're using ("{:?}"
) uses the curly braces to determine where the variable we give it will be placed in the string, and the :?
between the curly braces is a called a formatter. In this case, :?
is the Debug
formatter, which means we'll get the Debug
trait implementation of the StringRecord
type printed out to its own line.
The Debug
trait is often used for debugging purposes, which is why we see the StringRecord
struct name in the console output when we run the program. It's the same formatter used in the dbg!
macro, if you're familiar with that already.
cargo run --bin upload-pokemon-data
Finally, if no errors have occurred we need to return Ok(())