1

I want to be able to iterate over an array of parsers trying to find a delegate parser from a main parsing function. The list of parsers is known at compile-time, so I want this to be a constant.

I've tried some varieties of this but I can't get it to work:

const ALL_PARSERS: [&Parser; 1] = [&CommentParser {}];

How can I achieve this?

Notes:

  • Parser is a trait.
  • CommentParser is a struct implementing Parser.
  • there are other implementations of Parser but they are not shown for simplicity.
  • even though all parser implementations are known at compile-time, I just want to avoid explicitly trying one by one as that would make the code worse than I think it should be.

The error I get currently:

|
11 | const ALL_PARSERS: [&Parser; 1] = [&CommentParser {}];
   |                    ^^^^^^^^^^^^ the trait `parsers::Parser` cannot be made into an object
   |
   = note: method `parse` has generic type parameters

I can't see any generics in the parse method:

pub trait Parser {
    fn opening_char(self: &Self) -> char;
    fn parse(&mut self, env: impl ParserEnv) -> ParseResult;
}

If I make the types values:

const ALL_PARSERS: [Parser; 1] = [CommentParser {}];

The error becomes:

11 | const ALL_PARSERS: [Parser; 1] = [CommentParser {}];
|                    ^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `(dyn parsers::Parser + 'static)`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Renato
  • 12,940
  • 3
  • 54
  • 85
  • 1
    Any particlar reason why the parsers seen to be stored as *references*? Couldn't you just store the parsers themselves in the array? – Frxstrem May 30 '19 at 20:30
  • I tried that but because it's a trait, it's not sized, so Rust complains about that. – Renato May 30 '19 at 20:31
  • i've edited the question with details about the errors. – Renato May 30 '19 at 20:38
  • It's hard to answer your question because it doesn't include a [MCVE]. We can't tell what crates (and their versions), types, traits, fields, etc. are present in the code. It would make it easier for us to help you if you try to reproduce your error on the [Rust Playground](https://play.rust-lang.org) if possible, otherwise in a brand new Cargo project, then [edit] your question to include the additional info. There are [Rust-specific MCVE tips](//stackoverflow.com/tags/rust/info) you can use to reduce your original code for posting here. Thanks! – Shepmaster May 30 '19 at 23:15
  • [The duplicate applied to your question](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e1724431af75035179e624e5aacfcb81). – Shepmaster May 30 '19 at 23:19
  • @Shepmaster I gave all details that seem relevant to the problem. I explained the error. I don't think you can demand that I ask the question in a way YOU are 100% satisfied with. I've been answering questions here for a long time and I know a simple question like this shouldn't need reproducible examples. Please keep your high standards to yourself and let others help if you don't think it's good enough for you. Thank you. – Renato May 31 '19 at 05:12
  • The question you marked duplicate of this doesn't even mention arrays. What's up with that? Looking at the answers there I still have no idea how that would have answered my question. – Renato May 31 '19 at 05:21

1 Answers1

2

Trait references need to be marked as &dyn, e.g. &dyn Parser:

trait Parser { }

struct CommentParser { }
impl Parser for CommentParser { }

const ALL_PARSERS: [&dyn Parser; 1] = [&CommentParser {}];

fn main() {
    for &parser in &ALL_PARSERS {
        // do something with parser
    }
}

Link to playground example


Also, as this answer to a related question states, you can't have a generic parameter in your trait if you want to make a trait reference, so you'll need to either add a generic type to the trait itself, or use a trait reference instead of impl in your parser type:

// original (with error)
trait Parser {
    // impl ParserEnv is an implicit generic type
    fn parse(&mut self, env: impl ParserEnv) -> ParseResult;

    // same as:
    //   fn parser<E: ParserEnv>(&mut self, env: E) -> ParserResult;
}

// alternative 1, with trait generic type
trait Parser<E: ParserEnv> {
    fn parse(&mut self, env: E) -> ParseResult;
}

// alternative 2, with trait reference
trait Parser {
    fn parser(&mut self, env: &dyn ParserEnv) -> ParserResult;
    // may need &dyn mut ParserEnv if you want to modify env as well
}

I think the second approach may be the best, since then you can store the parsers in the array without needing to assign a particular ParserEnv type to the parsers.

Frxstrem
  • 38,761
  • 9
  • 79
  • 119
  • Didn't work still... looks like my definition of Parser causes problems... see [modified playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4613cdfb89e439fa397804c6b8553e78) – Renato May 30 '19 at 21:02
  • @Renato I updated my answer to resolve the problems in your modified playground example. – Frxstrem May 30 '19 at 21:23
  • Thanks, finally working! My knowledge of trait is still too weak :) will read up a bit more on that. – Renato May 30 '19 at 21:26
  • This does **not** make a static array. It makes a constant. – Shepmaster May 30 '19 at 23:16