0

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);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Stacy J
  • 158
  • 7
  • *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 Oct 20 '17 at 18:32
  • 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 Oct 20 '17 at 18:53
  • Were you aware that you can [implement a trait for a box of that trait](https://stackoverflow.com/q/33041736/155423)? Then this function doesn't have to be the one that performs the boxing. – Shepmaster Oct 20 '17 at 18:56

1 Answers1

0

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))
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • @StacyProwell perhaps this question is really a duplicate of https://stackoverflow.com/q/27535289/155423 then? – Shepmaster Oct 20 '17 at 18:51
  • Not sure how I could return the value directly; wouldn't I still need to specify the lifetime? – Stacy J Oct 20 '17 at 18:51
  • Ah! Yes, "let the compiler guide you." Please keep in mind I'm coming from C++, where the compiler was designed to actively hate you. – Stacy J Oct 20 '17 at 18:55
  • @StacyProwell If you think that's a more comprehensive answer, you are encouraged to un-accept this and then you can mark your question as a duplicate of that. – Shepmaster Oct 20 '17 at 18:56