Description
Building an Atom feed currently incurs many many allocations. Nearly every field of the type contains a String
which mandates it be a separate and unique heap allocation. What's more, since all the .build()
methods take &self
they mandate cloning every single field in the builder. I think it would be good if atom_syndicator
exposed a separate *Writer
API for enabling feed-building without allocations. A sketch of what the API could look like is below:
pub struct FeedWriter<W> { /* ... */ }
impl<W: Write> FeedWriter<W> {
pub fn from_writer(writer: W) -> Self { /* ... */ }
pub fn title(&mut self) -> Result<TextWriter<'_, W>, Error> { /* set up the opening of a text tag */ }
pub fn id<Id: Display>(&mut self, id: Id) -> Result<&mut Self, Error> { /* ... */ }
// etc
// Automatically runs in the destructor of `FeedWriter`, but can be called here to handle errors.
pub fn finish(self) -> Result<W, Error> { /* ... */ }
}
pub struct TextWriter<'a, W: Write> { /* ... */ }
impl<W: Write> TextWriter<'_, W> {
pub fn set<Value: Into<Text>>(self, text: Value) -> Result<(), Error> { /* ... */ }
pub fn value<Value: Display>(&mut self, value: Value) -> Result<&mut Self, Error> { /* ... */ }
pub fn base<Base: Display>(&mut self, base: Base) -> Result<&mut Self, Error> { /* ... */ }
// etc
}
impl<W: Write> Drop for TextWriter<'_, W> {
fn drop(&mut self) {
// finish writing out the text tag
// In lieu of fallible destructors, errors from here can be pushed
// up into the FeedWriter and returned on the next call to any one
// of its methods.
}
}
Such an API would be usable like so:
let mut feed = FeedWriter::new(Vec::new());
feed.title()?.value("My example feed")?;
feed.id("http://example.com/")?;
feed.generator()?.value("My generator")?.version("1.0.0")?;
for entry in ["entry 1", "entry 2"] {
feed.entry()?.content()?.value(entry)?;
}
let bytes = feed.finish()?;
As an alternative design, it's possible to collate all errors within the FeedWriter
so that each individual method doesn't return a Result
, but instead the first error is returned at .finish()
. However that would mean it could force the user to do a lot more work than they need to do, since early returns on error conditions would not be possible.
Would such an API be possible to implement?