Reading and Writing fastq/fastq.gz in Rust
TL;DR
When processing fastq files, Python can become painfully slow when dealing with large fastq file sets. While writing a fastq parser from scratch is not difficult, there is a crate called rust-bio that we can use instead.
The usage is straightforward if you read the docs, but since reading and writing gz format is not supported by rust-bio alone, I will cover that as well.
dependencies
Cargo.toml
A Note on rust-bio's io::fastq
rust-bio provides a fastq parser. Since its Record type holds Strings, it may not be ideal in terms of allocation amortization and performance. The error handling is worth referencing, though, so if you need to optimize for speed, you might be better off writing your own parser.
Reading and Writing fastq
Record
The Record definition, which corresponds to a single read in fastq, looks like this:
Each member can be accessed via a function with the same name (e.g., id()). However, the seq() function returns a byte slice. Honestly, if they were going to do that, I wish everything was read as &[u8] from the start...
Reader
Here is a near-copy from the docs:
This example reads from standard input, but to read from a file (which is far more common), you can create a Reader with:
Write
Again, here is a near-copy from the docs:
Just like with the Reader, to write to a file:
Reading and Writing fastq.gz
We use the flate2 crate for handling gz and zip compression. We also use the anyhow crate for error handling so we can use the ? operator.
Cargo.toml
Read
fastq::Reader::new requires the input to implement the std::io::BufRead trait.
We create a function that reads through a GzDecoder if the file extension is gz, and reads normally with BufRead otherwise. We use flate2's MultiGzDecoder as the decoder.
Now we are ready to read from gz files. You can create a Reader like this:
Write
For the Writer, you can similarly create a BufWrite and pass it to fastq::io::Writer::new(). You could write a similar wrapper function, but since I cannot think of a case where you would not want to write as gz, and I have not actually written one, here is the code for directly passing a GzEncoder. I might write a proper wrapper eventually.
Summary
Just read the docs.