Generics with particular type "instantiation" prevention

Now I'm trying to write a (yet another) YAML parser.
YAML has several context for several items.

  • Context are always given at compile-time, not a runtime parameter
  • Some parser defines all context variants while other defines only subset of context.
    For example, YAML Ain’t Markup Language (YAML™) revision 1.2.2 nb-double-text is defined for 4 contexts out of 6.

I want to make this context non-runtime parameter but have no idea what's a good approach. I considered a few approaches but all seem not good enough.

  1. Make it enum and use const generics + specialization. I gave it up because
    • it's impossible to have enum as const generics parameter
    • it's impossible to specialize generics
  2. Make all Context marker structs, implement a trait Context.
    • I cannot find a way to define a way to omit certain context definition on parser.
  3. Make all context as a function suffix, use macro to make it a bit better.
    • That is promising while the cost of implementation is a bit high without procedure macro which is a bit too much of hassle.

Can anyone suggest me a good approach? Thanks in advance


1 implementation example

enum YamlContext {FlowIn, FlowOut, ...}

fn parse_nb_double_text<const ctx: YamlContext>(input: &mut &str) -> Result<&str>;
// how can I define this for only FlowIn/FlowOut/BlockKey/FlowKey??

2 implementation example

trait YamlContext: DoubleTextParse {}

struct FlowIn {}

impl YamlContext for FlowIn {}

fn parse_nb_double_text<Ctx: YamlContext>(input: &mut &str) -> Result<&str> {
  <Ctx as DoubleTextParse>::parse(input)
}

trait DoubleTextParse {
  fn parse(...);
}

// FIXME: I need to impl DoubleTextParse for all context, while I don't need it for BlockIn/BlockOut.

3 example

// define all _$context variants.
fn parse_nb_double_text_flow_in(...)
fn parse_nb_double_text_flow_out(...)
fn parse_nb_double_text_flow_key(...)
fn parse_nb_double_text_block_key(...)

// problem is that forwarding context is cumbersome and error-prone.

Depending on how many different combinations of contexts there are, you can have one trait per combination of contexts.. With the important caveat that I don't know YAML, something like this:

// The six contexts
enum BlockIn {}
enum BlockOut {}
enum BlockKey {}
enum FlowIn {}
enum FlowOut {}
enum FlowKey {}

// A general trait for all the Yaml contexts; probably has some methods
trait YamlContext {}

impl YamlContext for BlockIn {}
impl YamlContext for BlockOut {}
impl YamlContext for BlockKey {}
impl YamlContext for FlowIn {}
impl YamlContext for FlowOut {}
impl YamlContext for FlowKey {}

// The predicate saying "this is suitable for `nb-double-text`", which we'll
// impl for the corresponding four contexts
trait FlowOrKeyContext: YamlContext {
    fn nb_double_text(input: &mut str) -> Result<String, YamlError>;
}

impl FlowOrKeyContext for FlowIn {
    fn nb_double_text(input: &mut str) -> Result<String, YamlError> {
        nb_double_multi_line(input)
    }
}

impl FlowOrKeyContext for FlowOut {
    fn nb_double_text(input: &mut str) -> Result<String, YamlError> {
        nb_double_multi_line(input)
    }
}

impl FlowOrKeyContext for FlowKey {
    fn nb_double_text(input: &mut str) -> Result<String, YamlError> {
        nb_double_one_line(input)
    }
}

impl FlowOrKeyContext for BlockKey {
    fn nb_double_text(input: &mut str) -> Result<String, YamlError> {
        nb_double_one_line(input)
    }
}

The downside to this, of course, is that if the groupings aren't consistent, you could have up to 2⁶-1 = 63 different traits. But if it's consistently "all the flow contexts", "all the key contexts", and things like that, then this might not be so bad.

Thanks!

That looks promising, as actually YaML has only 2 sub-sets traits.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.