I have this trait representing the ability to render text (e.g. HTML) from a template, and an associated type TemplateData
to contain the data the template needs:
trait Template {
type TemplateData;
fn render(&self, data: &Self::TemplateData) -> String;
}
I want to have an implementation like this:
struct MyTemplateData<'a> {
title: &'a str,
author: &'a str,
last_updated: DateTime<Utc>,
body: &'a str,
}
struct MyTemplate;
impl Template for MyTemplate {
type TemplateData = MyTemplateData<'a>;
fn render(&self, data: &MyTemplateData<'_>) -> String {
/* snip */
}
}
MyTemplateData
needs a lifetime parameter because it has a &str
. That lifetime only needs to last the duration of the render
method since no references leave the method, so I use an anonymous lifetime for it. But as written above, this code doesn't work. The compiler complains that 'a
in the associated type specification is undeclared. If I declare it (impl<'a> Template for ...
), that still doesn't work, because then I get error[E0207]: the lifetime parameter 'a is not constrained by the impl trait, self type, or predicates.
The two ways to fix this are to add a lifetime parameter to the trait or to add it to the MyTemplate
struct it's being implemented for.
- Adding a lifetime to
MyTemplate
doesn't make much sense because it doesn't contain any reference members (or in fact, any members at all in this simple example). - Adding it to the trait doesn't seem to make sense because the lifetime really only applies to the
render
method, not trait-wide (for example, I could imagine having a second method likeget_rendered_size
that would have its own lifetime parameter). - Both ways have the very inconvenient drawback that lifetime annotations then have to be added wherever the trait/struct is used.
What it seems like I really need is a way to say, "the associated type will be MyTemplateData<'a>
for some lifetime 'a
, but just pick 'a
based on whatever render
needs where it's called.
Am I thinking about this right? Is there a way to do that?
CodePudding user response:
You are correct, and what you are looking for is called Generic Associated Types, or GAT for short.
The only problem is they're unstable and require nightly.
With them, it looks like:
#![feature(generic_associated_types)]
trait Template {
type TemplateData<'a>;
fn render(&self, data: &Self::TemplateData<'_>) -> String;
}
impl Template for MyTemplate {
type TemplateData<'a> = MyTemplateData<'a>;
fn render(&self, data: &MyTemplateData<'_>) -> String {
/* snip */
}
}
However, if you cannot use nightly, you have to choose one of the options you described (it is probably better to add the lifetime to the trait, since it is not tied to an instance but to the method, i.e. to an implementation of the trait).