Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't force adding "&" to start of collection for TemplateLoop #221

Conversation

gregelenbaas
Copy link

@gregelenbaas gregelenbaas commented Mar 15, 2019

Currently the TemplateLoop generator always appends & to every expression. This is inconvenient as it doesn't allow iteration over collections that are not owned by a struct but are passed by reference.

For example today this does not work:

welcome.rs

#[derive(Template)]
#[template(path = "apps/welcome/welcome.html")]
struct Landing<'a> {
    locales: &'a HashMap<String, Locale>,
}

pub fn index(req: HttpRequest<AppState>) -> Result<HttpResponse, Error> {

    let locales = &req.state().locales;

    let landing = Landing {
        locales: locales,
    };

    landing.respond_to(&req)
}

welcome.html

<h1>Welcome!</h1>
<div>
    {% for locale in locales.values() %}
        <p>
            <a hreflang="{{ locale.key }}" href="/{{ locale.key }}/">{{ locale.description }}</a>
        </p>
    {% endfor %}
</div>

This code will produce a cannot move out of borrowed context error.

The current work around is:
welcome.rs

#[derive(Template)]
#[template(path = "apps/welcome/welcome.html", print = "all")]
struct Landing<'a> {
    locales: &'a HashMap<String, Locale>,
}

// TODO: Delete after upgrading askama
struct LocaleAskama<'a> {
    description: &'a str,
    key: &'a str,
}

pub fn index(req: HttpRequest<AppState>) -> Result<HttpResponse, Error> {

    let locales = &req.state().locales;
    let mut locales_parsed = Vec::<LocaleAskama>::new();

    for locale in locales.values() {
        locales_parsed.push(LocaleAskama{
            description: locale.description.as_str(),
            key: locale.key.as_str(),
        });
    } 

    let landing = Landing {
        locales: locales_parsed,
    };

    landing.respond_to(&req)
}

The bad thing is that this would be a breaking change as it would require collections that are owned by the template to have the & prefix. I think this should be the correct pattern, I couldn't figure out any other way to check of the template field was a reference or owned.

Also, I noticed there is no CHANGELOG.md file, I can create one if you think it's a good idea.

…ollections to be passed directly to a template without needed to create new owned collection.
@djc
Copy link
Collaborator

djc commented Mar 15, 2019

There's actually a fairly extensive change log here: https://github.com/djc/askama/releases, but maybe it would help to link to it from the README to make it more discoverable?

@djc
Copy link
Collaborator

djc commented Mar 15, 2019

Although I agree that this should work, I don't think this is the right direction for a solution. Can you show in more detail what causes the "cannot move out of borrowed context" problem in this case?

@gregelenbaas
Copy link
Author

Great, I'll update https://github.com/djc/askama/releases . Definitely not sure if this is the right solution either but will be great to be able to use referenced collections. When I use 0.8 Askama here is what I get:

Code:

#[derive(Template)]
#[template(path = "apps/welcome/welcome.html", print = "all")]
struct Landing<'a> {
    locales: &'a HashMap<String, Locale>,
}

pub fn index(req: HttpRequest<AppState>) -> Result<HttpResponse, Error> {

    let locales = &req.state().locales;

    let landing = Landing {
        locales: locales,
    };

    landing.respond_to(&req)
}

Template

<h1>Welcome!</h1>
<div>
    {% for locale in locales.values() %}
        <p>
            <a hreflang="{{ locale.key }}" href="/{{ locale.key }}/">{{ locale.description }}</a>
        </p>
    {% endfor %}
</div>

Askama Output

[Lit("", "<h1>Welcome!</h1>\r\n<div>", "\r\n    "), Loop(WS(false, false), Name("locale"), MethodCall(Var("locales"), "values", []), [Lit("\r\n        ", "<p>\r\n            <a hreflang=\"", ""), Expr(WS(false, false), Attr(Var("locale"), "key")), Lit("", "\" href=\"/", ""), Expr(WS(false, false), Attr(Var("locale"), "key")), Lit("", "/\">", ""), Expr(WS(false, false), Attr(Var("locale"), "description")), Lit("", "</a>\r\n        </p>", "\r\n    ")], WS(false, false)), Lit("\r\n", "</div>", "")]
impl < 'a > ::askama::Template for Landing< 'a > {    fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> {
        include_bytes ! (
"C:\\Users\\GregElenbaas\\workspace\\walkwiki\\templates\\apps/welcome/welcome.html"
) ;
        writer.write_str("<h1>Welcome!</h1>\r\n<div>\r\n    ")?;
        for (locale, _loop_item) in ::askama::helpers::TemplateLoop::new((&self.locales.values()).into_iter()) {
            write!(
                writer,
                "\r\n        <p>\r\n            <a hreflang=\"{expr0}\" href=\"/{expr0}/\">{expr1}</a>\r\n        </p>\r\n    ",
                expr0 = &::askama::MarkupDisplay::new_unsafe(&locale.key, ::askama::Html),
                expr1 = &::askama::MarkupDisplay::new_unsafe(&locale.description, ::askama::Html),
            )?;
        }
        writer.write_str("\r\n</div>")?;
        Ok(())
    }
    fn extension() -> Option<&'static str> {
        Some("html")
    }
    fn size_hint() -> usize {
        8
    }
}
impl < 'a > ::std::fmt::Display for Landing< 'a > {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        ::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
    }
}
impl < 'a > ::askama::actix_web::Responder for Landing< 'a > {
    type Item = ::askama::actix_web::HttpResponse;
    type Error = ::askama::actix_web::Error;
    fn respond_to<S>(self, _req: &::askama::actix_web::HttpRequest<S>) -> ::std::result::Result<Self::Item, Self::Error> {
        use ::askama::actix_web::TemplateIntoResponse;
        self.into_response()
    }

Compiler Error

error[E0507]: cannot move out of borrowed content
  --> src\apps\welcome\mod.rs:11:10
   |
11 | #[derive(Template)]
   |          ^^^^^^^^ cannot move out of borrowed content

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.
error: Could not compile `walkwiki`.

To learn more, run the command again with --verbose.

@djc
Copy link
Collaborator

djc commented Mar 18, 2019

So now can you put the generated source code in your own source code, comment out the derive and template attributes so that we can see the precise error the compiler generates?

@gregelenbaas
Copy link
Author

Sure, here you go.
Code:

struct Landing<'a> {
    base: BaseTemplateData<'a>,
    locales: &'a HashMap<String, Locale>,
}

impl < 'a > ::askama::Template for Landing< 'a > {
    fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> {
        include_bytes ! (
"C:\\Users\\GregElenbaas\\workspace\\walkwiki\\templates\\apps/welcome/welcome.html"
) ;
        writer.write_str("<div>\r\n    ")?;
        for (locale, _loop_item) in ::askama::helpers::TemplateLoop::new((&self.locales.values()).into_iter()) {
            write!(
                writer,
                "\r\n        <p>\r\n            <a hreflang=\"{expr0}\" href=\"/{expr0}/\">{expr1}</a>\r\n        </p>\r\n    ",
                expr0 = &::askama::MarkupDisplay::new_unsafe(&locale.key, ::askama::Html),
                expr1 = &::askama::MarkupDisplay::new_unsafe(&locale.description, ::askama::Html),
            )?;
        }
        writer.write_str("\r\n</div>")?;
        Ok(())
    }
    fn extension() -> Option<&'static str> {
        Some("html")
    }
    fn size_hint() -> usize {
        8
    }
}
impl < 'a > ::std::fmt::Display for Landing< 'a > {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        ::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
    }
}
impl < 'a > ::askama::actix_web::Responder for Landing< 'a > {
    type Item = ::askama::actix_web::HttpResponse;
    type Error = ::askama::actix_web::Error;
    fn respond_to<S>(self, _req: &::askama::actix_web::HttpRequest<S>) -> ::std::result::Result<Self::Item, Self::Error> {
        use ::askama::actix_web::TemplateIntoResponse;
        self.into_response()
    }
}

pub fn index(req: HttpRequest<AppState>) -> Result<HttpResponse, Error> {

    let locales = &req.state().locales;
    let component_locales = &req.state().component_locales;

    let landing = Landing {
        base: BaseTemplateData {
            app: &component_locales["en"]["land"],
            chrome: &component_locales["en"]["chrm"],
            body_class: "welcome-page",
            href_langs: Vec::new(),
            page_lang: &"x-default".to_string(),
            style_sheet: &req.state().style_sheet,
        }, 
        locales: locales,
    };

    landing.respond_to(&req)
}

Error:

error[E0507]: cannot move out of borrowed content
  --> src\apps\welcome\mod.rs:29:74
   |
29 |         for (locale, _loop_item) in ::askama::helpers::TemplateLoop::new((&self.locales.values()).into_iter()) {
   |                                                                          ^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.
error: Could not compile `walkwiki`.

To learn more, run the command again with --verbose.

@gregelenbaas
Copy link
Author

Just checking @djc if you had any feedback

@djc
Copy link
Collaborator

djc commented Apr 3, 2019

I suppose the problem here is that you want (&self.locales).values() but you're getting &self.locales.values() which is different in terms of ownership?

So far I've resisted adding & in the template language, and I don't like the idea of adding it. But right now I don't have any good ideas for workarounds for your problem.

@gregelenbaas
Copy link
Author

I understand not wanting to have to think about "&" in Askama templates. The issue is that I have some long lived collections of strings that are loaded at server start that are used to populate the text for various templates. Askama currently can't loop through collections held by the template struct that are references with lifetime annotations, and not owned by the struct itself. This PR fixes that issue but is breaking. I've been trying to research a way in Rust that we could detect a value is a reference instead of owned at run time. I am going to look into TryFrom but am not sure if that's the right way to go.

@djc
Copy link
Collaborator

djc commented Apr 29, 2019

I wonder what we could do here if we add filters to the mix. That way, you could at least write Rust code that adapts your iterator, in Rust code, possibly to some trait defined in askama that doesn't get the mandatory reference treatment. Does that make sense?

@zakcutner
Copy link

zakcutner commented Oct 17, 2019

My issue with the current implementation is that it seems into_iter() only happens to work with types like Vec due to its implementation under the hood which actually uses iter().

Conversely, I get the rational behind this and it's definitely a nice idea to not mutate iterables used in templates. However, I think that trying to implement for loops in a different way to how Rust does will ultimately always cause conflict with the language and confusion for users of this library.

It would be great to get this resolved because, at least for me, it currently presents quite a barrier to using Askama 😞

@djc djc changed the base branch from master to main September 14, 2020 07:19
@djc djc closed this in #401 Dec 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants