utkarshkukreti / markup.rs

A blazing fast, type-safe template engine for Rust.
Apache License 2.0
363 stars 15 forks source link

Owned types as parameters #30

Closed drdo closed 1 year ago

drdo commented 1 year ago

Maybe I misunderstood something, but it seems that markup.rs always adds a reference to whatever types you use.

Consider the following code:

markup::define! {
    Foo<X: Render, I: Iterator<Item = X>>(items: I) {
        @for i in items {
            p { @i }
        }
    }
}

This does not compile, it complains that Iterator is not implemented for &I.

Why is this the case? Why can't I just pass the type I itself?

What would be the correct way to implement something similar to the code above?

utkarshkukreti commented 1 year ago

Hi,

This is because of how the Render trait is implemented: a template can be rendered multiple times after it's created once. If the fields of the template were available directly inside the template, this would not be possible as all the fields would need to be moved out of the struct on the first render.

What kind of values will you be passing to this template? One solution could be to accept Cloneable iterators (many iterators in Rust are cheap to clone):

markup::define! {
    Foo<X: markup::Render, I: Iterator<Item = X> + Clone>(items: I) {
        @for i in items.clone() {
            p { @i }
        }
    }
}

fn main() {
    println!(
        "{}",
        Foo {
            items: vec![1, 2, 3].iter()
        }
    );
}

Cloning an iterator in this case is extremely cheap (just a couple of pointers IIRC).

drdo commented 1 year ago

I'm wondering. Why would a template need to be rendered multiple times?

Can you not just create and consume a new instance each time?

utkarshkukreti commented 1 year ago

I don't remember exactly, but I think the primary reason I went for this approach is because otherwise templates couldn't implement std::fmt::Display because Display::fmt takes &self.

drdo commented 1 year ago

Alright, thanks.