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

Cannot move out of borrowed content in for loop #107

Closed
iammichiel opened this issue Aug 20, 2018 · 17 comments · Fixed by #401
Closed

Cannot move out of borrowed content in for loop #107

iammichiel opened this issue Aug 20, 2018 · 17 comments · Fixed by #401

Comments

@iammichiel
Copy link

iammichiel commented Aug 20, 2018

Hi there,

I am probably not posting this in the right place but I seek help using this library.
I am trying to iterate over a list of x509Name's using the following code in my template :

{% for entry in certificate.subject_name().entries() %}

{% endfor %}

certificate being an instance of X509. I have the following error : cannot move out of borrowed content. I am pretty new to rust, this must be something easy but I have not yet full comprehension of the concept of ownership..

@djc
Copy link
Owner

djc commented Aug 21, 2018

This is a fine place to post this!

So Askama's Template trait takes a reference to your context struct and in general converts everything in the template code to references in order to make things work. It's possible that that doesn't work in this case. As a first step, try enabling the print = "code" attribute on the template() call, so you can see the generated code, then paste that generated code into your source file and comment out the derive call and related template() attribute. That should pinpoint to the exact location of the error. If you then paste the generated code here, I can also take another look and see if the code generator can be improved in this respect.

@iammichiel
Copy link
Author

iammichiel commented Aug 21, 2018

Thanks for your answer, Rust community is not a lie :)

I tried the print attribute as suggested and the following code was generated :

match &self.certificate {
    Some(cert) => {
        for (_loop_index, entry) in (&cert.subject_name().entries()).into_iter().enumerate() {
            writer.write_str("\n\n                ")?;
        }
    }
    None => {
        writer.write_str("\n        ")?;
    }
}

Removing the leading & solves the problem but I have no idea how to do this in Jinja like code.

@djc
Copy link
Owner

djc commented Aug 21, 2018

You can try doing it like this:

{% let entries = cert.subject_name().entries() %}
{% for entry in entries %}

{% endfor %}

But I'm not sure it'll work... In general, you cannot remove the & in the template code.

@iammichiel
Copy link
Author

Tried that as well, same error. cannot move out of borrowed content. My current work-around is creating a structure and extracting data in controller before rendering in the template.

@djc
Copy link
Owner

djc commented Aug 21, 2018

I think that's probably the best workaround for now. 👍

@bhansconnect
Copy link

I am also running into this issue. My code is trying to create an iterator in the template:

{% for i in 1..n %}

{% endfor %}

@lukehsiao
Copy link

lukehsiao commented Oct 19, 2018

Just wanted to follow-up on this issue with a similar problem. Given the following structs:

pub struct Coursework {
    pub instructor: String,
    pub name: String,
    pub semester: String,
    pub short: String,
}

#[derive(Debug, Template)]
#[template(path = "markdown/courses.md", print = "code")]
pub struct MarkdownCoursesTemplate {
    pub coursework: Vec<Coursework>,
}

and the following template

## Coursework

<table class="table table-hover">
{% for course in coursework %}
<tr>
  <td class='col-md-1'>{{ course.semester }}</td>
  <td><strong>{{ course.name }}</strong> ({{ course.short }}), {{ course.instructor }}</td>
</tr>
{% endfor %}
</table>

The generated code is

impl ::askama::Template for MarkdownCoursesTemplate {
    fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> {
        writer.write_str("## Coursework\n\n<table class=\"table table-hover\">")?;
        writer.write_str("\n")?;
        for (_loop_index, course) in (&self.coursework).into_iter().enumerate() {
            writer.write_str("\n")?;
            writer.write_str("<tr>\n  <td class=\'col-md-1\'>")?;
            write!(writer, "{}", &{ course.semester })?;
            writer.write_str("</td>\n  <td><strong>")?;
            write!(writer, "{}", &{ course.name })?;
            writer.write_str("</strong> (")?;
            write!(writer, "{}", &{ course.short })?;
            writer.write_str("),")?;
            writer.write_str(" ")?;
            write!(writer, "{}", &{ course.instructor })?;
            writer.write_str("</td>\n</tr>")?;
            writer.write_str("\n")?;
        }
        writer.write_str("\n")?;
        writer.write_str("</table>")?;
        Ok(())
    }
    fn extension(&self) -> Option<&str> {
        Some("md")
    }
}

This fails compilation with

error[E0507]: cannot move out of borrowed content
  --> src/markdown.rs:17:37
   |
17 |             write!(writer, "{}", &{ course.semester })?;
   |                                     ^^^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> src/markdown.rs:19:37
   |
19 |             write!(writer, "{}", &{ course.name })?;
   |                                     ^^^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> src/markdown.rs:21:37
   |
21 |             write!(writer, "{}", &{ course.short })?;
   |                                     ^^^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> src/markdown.rs:24:37
   |
24 |             write!(writer, "{}", &{ course.instructor })?;
   |                                     ^^^^^^ cannot move out of borrowed content

error: aborting due to 4 previous errors

because of course being moved into the &{ course._ } block.

Had the generated code been

impl ::askama::Template for MarkdownCVTemplate {
    fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> {
        writer.write_str("## Coursework\n\n<table class=\"table table-hover\">")?;
        writer.write_str("\n")?;
        for (_loop_index, course) in (&self.coursework).into_iter().enumerate() {
            writer.write_str("\n")?;
            writer.write_str("<tr>\n  <td class=\'col-md-1\'>")?;
            write!(writer, "{}", course.semester)?; // <--
            writer.write_str("</td>\n  <td><strong>")?;
            write!(writer, "{}", course.name)?; // <--
            writer.write_str("</strong> (")?;
            write!(writer, "{}", course.short)?; // <--
            writer.write_str("),")?;
            writer.write_str(" ")?;
            write!(writer, "{}", course.instructor)?; // <--
            writer.write_str("</td>\n</tr>")?;
            writer.write_str("\n")?;
        }
        writer.write_str("\n")?;
        writer.write_str("</table>")?;
        Ok(())
    }
    fn extension(&self) -> Option<&str> {
        Some("md")
    }
}

it would have compiled and I believe would have worked as I expected. Coming from using Jinja in Python, I suspect that there is way to structure my code such that this works. Is there a recommended workaround for this type of scenario? Must these variables be copyable?

@djc
Copy link
Owner

djc commented Oct 19, 2018

@lukehsiao I think that is essentially the problem from #132, can you comment there and try the suggested fix?

@P-E-Meunier
Copy link

@djc: what's the purpose of _loop_item? It doesn't seem to be even accessible from template code.

I know very little about askama internals, so there must be a good reason not to translate loops to just loops, what is it?

@djc
Copy link
Owner

djc commented Mar 8, 2019

They are translated to just loops. _loop_item is accessible to templates as loop.index and friends.

The reason for this issue (IIRC) is that Template::render() by design only takes an immutable reference of the context type. I designed it this way so that eventually it might be possible to render a single instance multiple times (for something like functional reactive UIs), and conceptually it seems sensible to me that the act of rendering a template ought not to mutate the object it renders from.

@P-E-Meunier
Copy link

Actually, this is not quite the case. If the purpose of _loop_item is just to supply what it's supplying, then this could be as well achieved by the user with for (i, y) in x.iter().enumerate(). This doesn't mutate the template (I agree with you on that), but it creates a mutable iterator.

At the moment, the example above fails because x means self.x, and that is moved to the iterator.

How about the simpler translation where you don't prefix all variables with self (let the user do that, I'm sure they can figure it out), and translate constructs more directly? Why does this fail?

In my example above, I would write for (i, y) in self.x.iter().enumerate(), and that would get copy-pasted directly without any parsing or modification. This would let the Rust compiler tell me about parsing errors.

@djc
Copy link
Owner

djc commented Mar 8, 2019

Because that adds a bunch of boiler plate for the common case.

I don't have the exact issue here paged in at the moment and it sounds like you do, so maybe you can explain in more detail why your self.x.iter() would work? (You could write self.x.iter() for the loop expression today, IIRC, and the code generated around that should not cause it to fail compilation in my head at least, but as I said, I don't remember the subtleties here right now.)

@P-E-Meunier
Copy link

This is because, AFAICT, in the current implementation self.x seems to be moved to the askama::helpers::TemplateLoop iterator, because of a syntax issue. I don't have much more detail, as rustc doesn't provide more (which I totally understand at this stage of development of Askama, and with the current maturity of code generation in Rust).

However, for y in self.x.iter() just borrows self.x, so that wouldn't be a problem.

Because that adds a bunch of boiler plate for the common case.

I'm not sure about that, since it just adds a self. I can see how the simple examples in the README would look a bit longer with the extra self, but given how I've been using Askama (I'm trying to convert https://nest.pijul.com, it's already partially served by Askama), my common case when writing code would be greatly simplified if the syntax was just Rust, since I wouldn't have to look at generated code to figure out how my code was translated, why things were moved/mutated, etc.

Also, doing less translation would give you the extra benefit of generating Rust code with the correct source positions, resulting in nicer error messages.

@djc
Copy link
Owner

djc commented Mar 9, 2019

You can try to add print = "code" to the #[template()] attribute, compile it, copy the generated code from the terminal back into your source to get a better error message. If you can then suggest alternative code generation techniques that yield better results, that would be great!

Sorry, I'm not going to change the syntax to always require self.

@drahnr
Copy link
Contributor

drahnr commented Mar 16, 2019

Another example use case which breaks: Types with multiple iterators yielding different types of iterators have to be disambiguated by type annotation which is not possible afaik

struct Z{
 children: Vec<Vec<u8>>,
}
impl Z {
pub fn iter_vec() -> { unimplemented!()} 
pub fn iter_items() -> { unimplemented!()} 
}

IntoIterator can only be implemented for one of those, for the other the above is true and so far I had to resort to iterating by using indices.

@gregelenbaas
Copy link

I think this is related to #221

@djc
Copy link
Owner

djc commented Dec 12, 2019

I think we could fix this by using autoref-based specialization to dispatch to proper handling in Askama. If someone wants to take a whack at implementing this, I'd be happy to provide further guidance and review!

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 a pull request may close this issue.

7 participants