Phil L
Phil L

Reputation: 89

Provide inheritance-like behavior for traits in Rust

So let's say I have two little traits, Parser and Trigger. A Parser takes a slice of strings, do some parsing and returns the slice of strings without the consumed tokens. On error, it returns an error. A Trigger is just like a Parser, except it does not expect any tokens, therefore it does something and maybe return an error. Every Trigger can be treated as a Parser and I want to define the function which makes that rule real. This is what my intuition would have me do:

pub trait Parser<'a> {
  fn parse(&mut self, &'a [String]) -> Result<&'a [String], Error>;
}

pub trait Trigger {
  fn pull(&mut self) -> Option<Error>;
}

impl<'a> Parser<'a> for Trigger {
  fn parse(&mut self, tokens: &'a [String]) -> Result<&'a [String], Error> {
    match self.pull() {
      Some(err) => Err(err),
      _         => Ok(tokens) //Returns the tokens untouched
    }
  }
}

I get this error message:

parsers.rs:37:33: 37:40 error: explicit lifetime bound required
parsers.rs:37 impl<'a> Parser<'a> for Trigger {
                                      ^~~~~~~

Can you please explain what is going on and what would be the idiomatic way of achieving what I want. Thanks :)

Upvotes: 2

Views: 494

Answers (1)

Vladimir Matveev
Vladimir Matveev

Reputation: 128051

Traits in Rust are not regular types. They either can be used as generic bounds or as trait objects, which are regular types but which also require some kind of indirection to work with them (a reference or a Box). You, on the other hand, are trying to implement Parser for bare Trigger. Bare traits are dynamically sized types and in general they can't be used everywhere where ordinary sized types can.

What you really want is to implement Parser for every type which implements Trigger. This can be written in a very straightforward way:

impl<'a, T> Parser<'a> for T where T: Trigger {
  fn parse(&mut self, tokens: &'a [String]) -> Result<&'a [String], Error> {
    match self.pull() {
      Some(err) => Err(err),
      _         => Ok(tokens)
    }
  }
}

This is so-called blanket impl pattern, and it is used widely across the standard library. There are limitations for it, though, for example, you won't be able to reimplement Parser for any particular Trigger.

BTW, consider moving 'a lifetime argument in the trait to its method. The way you use this parameter is rarely useful (of course, it depends on your actual code, but still).

Upvotes: 6

Related Questions