Hi everyone, i'm new to Rust. I was solving an exercise where you need to create a builder for HTML strings. Although the code presented below does the job, but i'm not sure whether to_indented_string function can be refactored to be more idiomatic Rust? Also, the compiler asked me to specify lifetimes, i pleased it, however, and i have no idea whether i have done it in a meaningful way. My best guess is that it has something to do with a promise that the members of HtmlElement wont live longer than a HtmlElement object itself, that's why the structs are promised to live for the time 'a same as name, text, elements. Can you please review this code, thank you.
Current code allocates lots of intermediate buffers, copy it into another buffer and drop it. It's not necessarily bad, but you can optimize it for performance.
for e in self.elements.iter() is unnecessary since references to containers implement IntoIterator. You can just write for e in &self.elements.
It's not elegant to push_str(format!(…)), because it allocates needlessly. Use write!()/writeln!() instead.
For the same reason, it would be better if to_indented_string() didn't return an owned String, because then the recursion needs to allocate, too. You should append to a string through a mutable reference instead.
Explicit return at the end of the function is not needed. Rust is an expression language, the last expression in the function will be its return value.
You should implement to_string() using the ToString trait, not as an inherent method. Better yet, implement Display and let the blanket impl handle it.
Here's the updated code. I reproduce the most important part, the to_string_indented() method below:
fn to_indented_string<W: Write>(&self, indent_lvl: usize, result: &mut W) -> Result {
Self::write_indent(indent_lvl, &mut *result)?;
writeln!(result, "<{}>", self.name)?;
if !self.text.is_empty() {
Self::write_indent(indent_lvl + 1, &mut *result)?;
writeln!(result, "<{}>", self.text)?;
}
for e in &self.elements {
e.to_indented_string(indent_lvl + 1, &mut *result)?;
}
Self::write_indent(indent_lvl, &mut *result)?;
writeln!(result, "</{}>", self.name)
}
Don't think of 'a as "lives for 'a lifetime", because temporarily borrowed references are not in control of their lifetime. It's better to think of it as "it's borrowed from somewhere else, which can be found by looking where the 'a came from".
In your case use of temporary non-owning views into external strings (&str) in the struct is probably a mistake. This will be horrendously painful to use in any real-world case (that doesn't rely on the special case of &'static str hardcoded into the program), because the first element you create will freeze the source of the strings as the only one you can use, and also require it to be shared+immutable. Without an extra abstraction like arena, you won't even be able to create a second element. And even with an arena, you'll likely run into self-referential struct problem.
Until you think Rust is easy, don't use references in structs at all. Treat struct …<'a> as a red flag.
Use String in structs. Use &str in function arguments.
Okay, i see, the compiler didnt need me to specify lifetimes anymore if i change struct fields to String. I will try to avoid struct …<'a> until im more experienced, thanks.