djc / askama

Type-safe, compiled Jinja-like templates for Rust
Apache License 2.0
3.47k stars 219 forks source link

Generic iterator field for a template #881

Open livingsilver94 opened 1 year ago

livingsilver94 commented 1 year ago

I need to compute the string values out of a slice of Messages by filtering and mapping. At the end of the chain, I have an std::iter::Map<...> which can be seen as an impl Iterator<Item = String>. At this point I cannot figure out a way to pass this iterator to a Template to loop over it:

#[derive(Template)]
#[template(path = "enum.c.j2")]
struct Enum<'a, E: Iterator<Item = String>> {
    count_prefix: String,
    entries: &'a E,
    object_name: String,
}

The compiler returns:

error[E0507]: cannot move out of a shared reference
  --> lib/src/generator/c.rs:14:10
   |
14 | #[derive(Template)]
   |          ^^^^^^^^
   |          |
   |          value moved due to this method call
   |          move occurs because value has type `E`, which does not implement the `Copy` trait
   |
note: `into_iter` takes ownership of the receiver `self`, which moves value
  --> /builddir/build/BUILD/rustc-1.72.1-src/library/core/src/iter/traits/collect.rs:271:18
   = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)

Is there any way to do this, or am I forced to compute the entire list up front?

djc commented 1 year ago

What does the relevant template code look like?

livingsilver94 commented 1 year ago

Tree:

[Lit(Lit { lws: "", val: "typedef enum\n{", rws: "\n    " }), Loop(Loop { ws1: Ws(None, None), var: Name("entry"), iter: Var("entries"), cond: None, body: [Lit(Lit { lws: "\n        ", val: "SWM_CANPDU_", rws: "" }), Expr(Ws(Some(Suppress), Some(Suppress)), Var("entry")), Lit(Lit { lws: "", val: ",", rws: "\n    " })], ws2: Ws(None, None), else_nodes: [], ws3: Ws(None, None) }), Lit(Lit { lws: "\n    ", val: "", rws: "" }), Comment(Comment { ws: Ws(None, None), content: "{#" }), Lit(Lit { lws: "\n    ", val: "SWM_CANPDU_", rws: "" }), Expr(Ws(Some(Suppress), Some(Suppress)), Var("count_prefix")), Lit(Lit { lws: "", val: "_NUMBER_ID,\n}\nSWM_CANPDU_", rws: "" }), Expr(Ws(Some(Suppress), Some(Suppress)), Var("object_name")), Lit(Lit { lws: "", val: "List_priv_t;", rws: "" })]

Code:

impl < 'a, E : Iterator < Item = String > > ::askama::Template for Enum< 'a, E > {
    fn render_into(&self, writer: &mut (impl ::std::fmt::Write + ?Sized)) -> ::askama::Result<()> {
        include_bytes! ("/home/path/lib/templates/enum.c.j2") ;
        writer.write_str("typedef enum\n{\n    ")?;
        {
            let mut _did_loop = false;
            let _iter = (&self.entries).into_iter();
            for (entry, _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {
                _did_loop = true;
                ::std::write!(
                    writer,
                    "\n        SWM_CANPDU_{expr0},\n    ",
                    expr0 = &::askama::MarkupDisplay::new_unsafe(&(entry), ::askama::Html),
                )?;
            }
            if !_did_loop {
            }
        }
        ::std::write!(
            writer,
            "\n    \n    SWM_CANPDU_{expr1}_NUMBER_ID,\n}}\nSWM_CANPDU_{expr2}List_priv_t;",
            expr1 = &::askama::MarkupDisplay::new_unsafe(&(self.count_prefix), ::askama::Html),
            expr2 = &::askama::MarkupDisplay::new_unsafe(&(self.object_name), ::askama::Html),
        )?;
        ::askama::Result::Ok(())
    }
    const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = 
    Some("c")
    ;
    const SIZE_HINT: ::std::primitive::usize = 
    126
    ;
    const MIME_TYPE: &'static ::std::primitive::str = 
    "text/plain; charset=utf-8"
    ;
}
impl < 'a, E : Iterator < Item = String > > ::std::fmt::Display for Enum< 'a, E > {
    #[inline]
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        ::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
    }
}
djc commented 1 year ago

I just mean the contents of enum.c.j2.

livingsilver94 commented 1 year ago

Oh sorry about that. Here it is:

typedef enum
{
    {% for entry in entries %}
        SWM_CANPDU_{{- entry -}},
    {% endfor %}
    {# The last entry of the enum is the entry count. #}
    SWM_CANPDU_{{- count_prefix -}}_NUMBER_ID,
}
SWM_CANPDU_{{- object_name -}}List_priv_t;
livingsilver94 commented 1 year ago

I guess I could have {% for entry in entries.clone() %} in the template, since cloning std::iter::Map is cheap given its 32-bit size. I'm open to more elegant solutions though, if any.

djc commented 1 year ago

The notion of an &impl Iterator itself feels fairly confused, since you need a &mut reference to actually iterate. One thing that could work is to have the field be more like messages: &[Message<'a>] and then expose an iter(&self) method on Enum which yields the owned iterator, for use in the template.