Can't pass temporary sturct between methods, because it borrows components of `&self`

I have a struct that keeps quick_xml::Writer inside. In Writer, there's a helper method that creates a struct, but the struct borrows Writer itself. And I can't pass this struct in another method to reduce repetitions.

I tried returning the struct back, but since there are errors to propagate, it must be wrapped, and compiler wanted some weird return type, which I couldn't produce.

I'd probably just unite the methods, and have if-else before and after the repetitive code. But are there workarounds?

type MAYBE = Result<(), Box<dyn Error>>;

struct MyWriter {
	wr: Writer<Box<dyn Write + Send>>
}

impl MyWriter {
	...
	pub fn write_tags(&mut self, elt: &ElementWriter<Box<dyn Write + Send>>, tags: Tags) -> MAYBE {

		// this code repeats

		if tags.0.len() > 0 {
			elt.write_inner_content(|writer| {
				for (k, v) in &tags.0 {
					writer.create_element("tag").with_attributes(
						vec![("k", &k as &str), ("v", &v as &str)].into_iter()
						).write_empty()?;
				}
				Ok(())  // this is inside closure
			})?;
		}
		else { elt.write_empty()?; }
		Ok(())
	}


	pub fn write_node(&mut self, node: Node) -> MAYBE {
		let mut elt = self.wr.create_element(...);
		//            ^^^^^^^ first mutable borrow
		self.write_tags(elt, node.tags)?;
		// ^^^^^^^^^^^^ second mutable borrow
		Ok(())
	}

	pub fn write_way(&mut self, way: Way) -> MAYBE {
		let mut elt = self.wr.create_element(...);
		self.write_tags(elt, way.tags)?;
		Ok(())
	}
}

Seems you aren't using self inside of write_tags, so could you just make it a free-standing method or a function?

1 Like

Tried it, but apparently elt.write_inner_content causes move of elt:ElementWriter and it carries Writer from MyStruct with itself.

90 | /         elt.write_inner_content(|writer| {
91 | |             for (k, v) in &tags.0 {
92 | |                 writer.create_element("tag").with_attributes(
93 | |                     vec![("k", &k as &str), ("v", &v as &str)].into_iter()
...  |
96 | |             Ok(())
97 | |         })?;
   | |__________^ move occurs because `*elt` has type `ElementWriter<'_, BufWriter<Box<dyn std::io::Write + std::marker::Send>>>`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `*elt` which is behind a shared reference
   --> src/main.rs:100:3
    |
100 |         elt.write_empty()?;
    |         ^^^^^^^^^^^^^^^^^ move occurs because `*elt` has type `ElementWriter<'_, BufWriter<Box<dyn std::io::Write + std::marker::Send>>>`, which does not implement the `Copy` trait

This doesn't sound right, but you're not giving the full code. Could you put it on https://www.rustexplorer.com?

2 Likes

That's probably because of your

		// this code repeats

If a non-Copy type has a self-taking method, calling the method consumes the value, so you can't reuse it in a loop. You'll probably have to refactor in order to not do that.

Playground approximation.

1 Like

I've cleaned my code up to minimize the code, but can't get rid of quick_xml structs.

That's why I linked to rustexplorer , had to fix some other errors, but now it shows what you mentioned.

As @quinedot said, it can't work that way, since write_inner_content consumes the ElementWriter, so you don't stand a chance of calling write_node or write_way repeatedly on your OsmWriter. Maybe the code they linked is a way forward?

1 Like

Yes, I see. So, this convenience method works in a very limited way -- you can easily create an XML element and add children elements only once. I guess, the way it's done via a closure, it will also take ownership of my local objects, if I put them in closure body.

For those who'll read this later, here's my refactored version of the code. I extract Tags in a match clause over variants of OsmObj, and then just use same for ... in loop on Tags.

There's quite some repetition, and I think it can be abstracted to one method for tag, nd and member elements.

	pub fn write_elt(&mut self, osmobj: &OsmObj) -> Maybe {
		let (n, l, tags, nodes, members) = match osmobj {
			OsmObj::Node(n) => ("node", 4, n.tags.0.clone(), vec![], vec![]),
			OsmObj::Way(w) => ("way", 3, w.tags.0.clone(), w.nodes.clone(), vec![]),
			OsmObj::Relation(r) => ("relation", 8, r.tags.0.clone(), vec![], r.members.clone())
		};

		{
			let mut elt = BytesStart::owned(n, l);
			let inner = match osmobj {
				OsmObj::Node(node) => {
					elt.push_attribute(("id", &node.id.to_string() as &str));
					elt.push_attribute(("lon", &node.lon.to_string() as &str));
					elt.push_attribute(("lat", &node.lat.to_string() as &str));
					node.tags.0.len() > 0
				},
				OsmObj::Way(way) => {
					elt.push_attribute(("id", &way.id.to_string() as &str));
					way.nodes.len() > 0 || way.tags.0.len() > 0
				},
				OsmObj::Relation(rel) => {
					elt.push_attribute(("id", &rel.id.to_string() as &str));
					rel.members.len() > 0 || rel.tags.0.len() > 0
				}
			};

			if !inner {
				self.wr.write_event(Event::Empty(elt))?;
				return Ok(())
			}
			self.wr.write_event(Event::Start(elt))?;
		}
		
		for (k, v) in tags {
			let mut nelt = BytesStart::owned("tag", 3);
			nelt.push_attribute(("k", &k.to_string() as &str));
			nelt.push_attribute(("v", &v.to_string() as &str));
			self.wr.write_event(Event::Empty(nelt))?;
		}
		for n in nodes {
			let mut nelt = BytesStart::owned("nd", 2);
			nelt.push_attribute(("ref", &n.to_string() as &str));
			self.wr.write_event(Event::Empty(nelt))?;
		}
		for n in members {
			let mut nelt = BytesStart::owned("member", 6);
			let mt = match n.mtype { MemberType::Node => "node", MemberType::Way => "way", MemberType::Relation => "relation"};
			nelt.push_attribute(("type", mt as &str));
			nelt.push_attribute(("ref", &n.refnum.to_string() as &str));
			nelt.push_attribute(("role", &n.role as &str));
			self.wr.write_event(Event::Empty(nelt))?;
		}
		self.wr.write_event(Event::End(BytesEnd::owned(n.as_bytes().to_vec())))?;
		Ok(())
	}