askama
askama copied to clipboard
Cannot move out of dereference when mixing loops with locks or similar
I have some shared state where I'd like to take the lock, render the template and release the lock all ideally without too much stuff being copied around. For example:
use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
use askama::Template;
#[derive(Template)]
#[template(source = "{% for v in foo.values() %}{{ v }}{% endfor %}", ext = "txt", print = "code")]
struct Example<'a> {
foo: RwLockReadGuard<'a, HashMap<u32, String>>
}
fn main() {
let lock = RwLock::new(HashMap::new());
let example = Example {
foo: lock.read().expect("Lock poisoned")
};
println!("{}", example.render().expect("Rendering failed"));
}
This does not work:
error[E0507]: cannot move out of a shared reference
--> src\main.rs:5:10
|
5 | #[derive(Template)]
| ^^^^^^^^ move occurs because value has type `std::collections::hash_map::Values<'_, u32, std::string::String>`, which does not implement the `Copy` trait
The generated code looks like this:
use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
use askama::Template;
//#[derive(Template)]
//#[template(source = "{% for v in foo.values() %}{{ v }}{% endfor %}", ext = "txt", print = "code")]
struct Example<'a> {
foo: RwLockReadGuard<'a, HashMap<u32, String>>
}
fn main() {
let lock = RwLock::new(HashMap::new());
let example = Example {
foo: lock.read().expect("Lock poisoned")
};
println!("{}", example.render().expect("Rendering failed"));
}
impl < 'a > ::askama::Template for Example< 'a > {
fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> {
for (v, _loop_item) in ::askama::helpers::TemplateLoop::new((&self.foo.values()).into_iter()) {
write!(
writer,
"{expr0}",
expr0 = &::askama::MarkupDisplay::new_unsafe(&v, ::askama::Text),
)?;
}
Ok(())
}
fn extension(&self) -> Option<&'static str> {
Some("txt")
}
fn size_hint(&self) -> usize {
0
}
}
impl < 'a > ::askama::SizedTemplate for Example< 'a > {
fn size_hint() -> usize {
0
}
fn extension() -> Option<&'static str> {
Some("txt")
}
}
impl < 'a > ::std::fmt::Display for Example< 'a > {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
}
}
Now I'm quite new to rust, so I don't quite understand what the reason is behind taking a reference in the loop expression. What I assume the intention is, is that (&{}).into_iter()
is there to make sure whatever is passed in is either an Iterator or becomes one. However, reading up on IntoIterator, I'd expect something more like IntoIterator::into_iter({})
. Swapping it in the generated code seems to work:
use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
use askama::Template;
//#[derive(Template)]
//#[template(source = "{% for v in foo.values() %}{{ v }}{% endfor %}", ext = "txt", print = "code")]
struct Example<'a> {
foo: RwLockReadGuard<'a, HashMap<u32, String>>
}
fn main() {
let lock = RwLock::new(HashMap::new());
let example = Example {
foo: lock.read().expect("Lock poisoned")
};
println!("{}", example.render().expect("Rendering failed"));
}
impl < 'a > ::askama::Template for Example< 'a > {
fn render_into(&self, writer: &mut ::std::fmt::Write) -> ::askama::Result<()> {
for (v, _loop_item) in ::askama::helpers::TemplateLoop::new(IntoIterator::into_iter(self.foo.values())) {
write!(
writer,
"{expr0}",
expr0 = &::askama::MarkupDisplay::new_unsafe(&v, ::askama::Text),
)?;
}
Ok(())
}
fn extension(&self) -> Option<&'static str> {
Some("txt")
}
fn size_hint(&self) -> usize {
0
}
}
impl < 'a > ::askama::SizedTemplate for Example< 'a > {
fn size_hint() -> usize {
0
}
fn extension() -> Option<&'static str> {
Some("txt")
}
}
impl < 'a > ::std::fmt::Display for Example< 'a > {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
}
}
So in the code generation, instead of:
_ => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new((&{}).into_iter()) {{",
expr_code
)),
wouldn't:
_ => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new(IntoIterator::into_iter({})) {{",
expr_code
)),
be better? Or am I missing something?
Only using a shared reference to the template context type during template rendering is an invariant that I've kept in mind from the beginning, in the hope that this would at some point enable stuff like functional recursive template rendering. So this is very much by design.
A possible workaround here would be to create a view type that holds references into the original type and derive the Template
on that. Hope that helps.
In terms of workarounds I actually went for use std::ops::Deref;
and then using .deref()
in the template. But to me the question remains: self
is already taken by reference in render_into
, why take another reference and call into_iter()
instead of what supposedly rust does for for
loops with calling the IntoIterator trait explicitly? This would also avoid calling into_iter
that are not IntoIterator
trait implementations.
I think the canonical issue for this is #107. It's kind of complex to correctly model the interactions with IntoIterator
and Iterator
itself, and without specialization I don't think there's much that can be improved. If you come up with a concrete proposal I'd be very interested though!