esteinig / nanoq

Minimal but speedy quality control for nanopore reads in Rust :bear:
MIT License
109 stars 9 forks source link

stdin / stdout #7

Closed esteinig closed 4 years ago

esteinig commented 4 years ago

Implement reading and writing streams

esteinig commented 4 years ago

@mbhall88 how would i go about implementing fastq::Reader::from_file(filename) with stdin - is there an idiomatic way in rust-bio?

esteinig commented 4 years ago

Oh man, it's literally in the first header comments of the code:

let reader = fastq::Reader::new(io::stdin());
esteinig commented 4 years ago

Hmmm but how to implement so it doesn't complain about different types assigned to reader in the match arms:

let reader = match cli.value_of("fastq") {
        Some(filename) => fastq::Reader::from_file(filename).expect("Error"),
        None | Some("-") => fastq::Reader::new(io::stdin())
    };

i.e. expected struct std::fs::File, found struct std::io::Stdin

mbhall88 commented 4 years ago

You need to indicate that your reader implements the trait Read.

use std::env;
use std::fs;
use std::io::{self, BufReader, Read};
use bio::io::fastq;

fn main() {
    let input = env::args().nth(1);
    let handle: Box<dyn Read> = match input {
        Some(filename) if filename != "-" => Box::new(fs::File::open(filename).unwrap()),
        _ => Box::new(BufReader::new(io::stdin())),
    };

    let reader = fastq::Reader::new(handle);

    for record in reader.records() {
        match record {
            Ok(r) => println!("{:?}", r),
            _ => println!("Bad record")
        }
    }
}

This is adapted as a combination of this SO answer and my own Fastx implementation in rasusa.

esteinig commented 4 years ago

Right ok and the Box has to do with how to allocate the type to memory? Why is that necessary? Thanks so much for taking the time!

esteinig commented 4 years ago

I changed this now to

let input_handle: Box<dyn Read> = match cli.value_of("fastq") {
        Some(filename) => Box::new(fs::File::open(filename).unwrap()),
        None =>  Box::new(BufReader::new(io::stdin()))
    };

Very nice!

mbhall88 commented 4 years ago

Box is a smart pointer. Basically, Rust wants to know how much memory to allocate (and where to allocate it stack/heap) during compilation (hence its memory safety). Because we are saying the return value just needs to implement the trait Read, the object being returned could theoretically be of any size. Therefore it is going to need to be allocated memory from the heap. From the always-wonderful documentation in the rust book about Box.

[When to use a Box<T>] When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type

NB: It would be nice to allow for being verbose about stdin with "-" in your implementation.

esteinig commented 4 years ago

Dude that's a great explanation. I was trying to figure this out. I'll add the conditional to add the "-" back into it. Thanks heaps!