0
votes

I have a public trait, Parser, that defines an external interface. I then have a private ParserImpl struct that implements the methods (actually, I have several implementations, which is the idea behind using the trait to abstract away).

use std::io;

pub trait Parser {
    // ...omitted
}

struct ParserImpl<R: io::Read> {
    // ...omitted
    stream: R,
}

impl<R: io::Read> ParserImpl<R> {
    // ...methods
    fn new(stream: R) -> ParserImpl<R> {
        ParserImpl {
            // ...omitted
            stream: stream,
        }
    }
}

impl<R: io::Read> Parser for ParserImpl<R> {
    // ...methods
}

To create a parser instance, I use a function to hide ParserImpl.

pub fn make_parser<'a, R>(stream: R) -> Box<Parser + 'a>
where
    R: io::Read + 'a,
{
    Box::new(ParserImpl::new(stream))
}

This is all well and good... and it works... but the make_parser function troubles me. I feel that there must be a simpler way to approach this and like I'm missing something important, as this seems like a potential pitfall whenever using a trait like io::Read to abstract away the source of data.

I understand the need to specify lifetimes (Parameter type may not live long enough?) but I am a bit stumped on whether I can have both a clean and simple interface, and also use a trait like io::Read.

Is there a "cleaner," or perhaps more idiomatic way, to use traits like io::Read that I am missing? If not, that's okay, but I'm pretty new to Rust and when I wrote the above function I kept thinking "this can't be right..."

To make this sample runnable, here's a main:

fn main() {
    use std::fs;
    let file: fs::File = fs::File::open("blabby.txt").unwrap();
    let parser = make_parser(file);
}
1
I use a function to hide ParserImpl — Why do you think that is a valuable thing to do in this case? this seems like a potential pitfall — What kind of pitfall do you imagine could happen? - Shepmaster
This is the simplified version. There are multiple parser implementations, all of which present the same Parser interface. The rest of the code needs to be agnostic to the specific parser being used. - Stacy J
Were you aware that you can implement a trait for a box of that trait? Then this function doesn't have to be the one that performs the boxing. - Shepmaster

1 Answers

0
votes

That is the idiomatic way of writing the code that has that meaning, but you may not want that meaning.

For example, if you don't need to create a boxed trait object, you can just return the parameterized value directly, or in this case just use the result of ParserImpl::new. This is my default form until I know I need dynamic dispatch provided by some trait object.

You could also require the 'static lifetime instead of introducing a new lifetime 'a, but this reduces the range of allowed types that you can pass into make_parser:

pub fn make_parser<R>(stream: R) -> Box<Parser>
where
    R: io::Read + 'static,
{
    Box::new(ParserImpl::new(stream))
}