Repository: martinohmann/hcl-rs
Pull request #55 (2022-08-20 21:38:37)

refactor!: move formatter into `format` module
BREAKING CHANGE: this breaks some public APIs.

### Breaking changes

- The types `hcl::ser::{Format, PrettyFormatter, PrettyFormatterBuilder}` were removed. The latter two have been replaced by `hcl::format::{Formatter, FormatterBuilder}`.
- `hcl::ser::Serializer` is not generic over `F: hcl::ser::Format` anymore.
- `hcl::ser::Serializer::with_formatter` now receives a `hcl::format::Formatter` as its only parameter.
- Instead of providing an `io::Write` to the `Serializer`, it's now required to provide it to the `Formatter` instead. See the docs of the new `format` module for a usage example.

The breaking changes should only have an impact on code that makes use of a custom HCL formatter. All other code using `hcl::{to_string, to_vec, to_writer}` should continue to work unchanged.

### TODOs

- [x] `format` module level docs
- [x] PR description
- [x] `ser` module doc wording; "from" vs "provided by" or better
- [x] additional doc tests


Patch:
diff --git a/src/ser/escape.rs b/src/format/escape.rs
similarity index 100%
rename from src/ser/escape.rs
rename to src/format/escape.rs
diff --git a/src/format/impls.rs b/src/format/impls.rs
index c3a652d..8f094a6 100644
--- a/src/format/impls.rs
+++ b/src/format/impls.rs
@@ -1,24 +1,22 @@
-use super::{private, Format};
+use super::{private, Format, Formatter};
 use crate::{
-    ser,
     structure::{
         Attribute, Block, BlockLabel, Body, Expression, Identifier, ObjectKey, RawExpression,
         Structure,
     },
     Map, Number, Result, Value,
 };
-use std::io;
+use std::io::{self, Write};
 
 impl private::Sealed for Body {}
 
 impl Format for Body {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         for structure in self.iter() {
-            structure.format(writer, fmt)?;
+            structure.format(fmt)?;
         }
 
         Ok(())
@@ -28,14 +26,13 @@ impl Format for Body {
 impl private::Sealed for Structure {}
 
 impl Format for Structure {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         match self {
-            Structure::Attribute(attr) => attr.format(writer, fmt),
-            Structure::Block(block) => block.format(writer, fmt),
+            Structure::Attribute(attr) => attr.format(fmt),
+            Structure::Block(block) => block.format(fmt),
         }
     }
 }
@@ -43,16 +40,15 @@ impl Format for Structure {
 impl private::Sealed for Attribute {}
 
 impl Format for Attribute {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
-        fmt.begin_attribute(writer)?;
-        fmt.write_ident(writer, &self.key)?;
-        fmt.begin_attribute_value(writer)?;
-        self.expr.format(writer, fmt)?;
-        fmt.end_attribute(writer)?;
+        fmt.begin_attribute()?;
+        fmt.write_ident(&self.key)?;
+        fmt.begin_attribute_value()?;
+        self.expr.format(fmt)?;
+        fmt.end_attribute()?;
         Ok(())
     }
 }
@@ -60,22 +56,21 @@ impl Format for Attribute {
 impl private::Sealed for Block {}
 
 impl Format for Block {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
-        fmt.begin_block(writer)?;
-        fmt.write_ident(writer, &self.identifier)?;
+        fmt.begin_block()?;
+        fmt.write_ident(&self.identifier)?;
 
         for label in &self.labels {
-            writer.write_all(b" ")?;
-            label.format(writer, fmt)?;
+            fmt.write_all(b" ")?;
+            label.format(fmt)?;
         }
 
-        fmt.begin_block_body(writer)?;
-        self.body.format(writer, fmt)?;
-        fmt.end_block(writer)?;
+        fmt.begin_block_body()?;
+        self.body.format(fmt)?;
+        fmt.end_block()?;
         Ok(())
     }
 }
@@ -83,14 +78,13 @@ impl Format for Block {
 impl private::Sealed for BlockLabel {}
 
 impl Format for BlockLabel {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         match self {
-            BlockLabel::Identifier(ident) => ident.format(writer, fmt),
-            BlockLabel::String(string) => string.format(writer, fmt),
+            BlockLabel::Identifier(ident) => ident.format(fmt),
+            BlockLabel::String(string) => string.format(fmt),
         }
     }
 }
@@ -98,19 +92,18 @@ impl Format for BlockLabel {
 impl private::Sealed for Expression {}
 
 impl Format for Expression {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         match self {
-            Expression::Null => Ok(fmt.write_null(writer)?),
-            Expression::Bool(b) => Ok(fmt.write_bool(writer, *b)?),
-            Expression::Number(num) => num.format(writer, fmt),
-            Expression::String(string) => string.format(writer, fmt),
-            Expression::Array(array) => format_array(writer, fmt, array),
-            Expression::Object(object) => format_object(writer, fmt, object),
-            Expression::Raw(raw) => raw.format(writer, fmt),
+            Expression::Null => Ok(fmt.write_null()?),
+            Expression::Bool(b) => Ok(fmt.write_bool(*b)?),
+            Expression::Number(num) => num.format(fmt),
+            Expression::String(string) => string.format(fmt),
+            Expression::Array(array) => format_array(fmt, array),
+            Expression::Object(object) => format_object(fmt, object),
+            Expression::Raw(raw) => raw.format(fmt),
         }
     }
 }
@@ -118,18 +111,17 @@ impl Format for Expression {
 impl private::Sealed for Value {}
 
 impl Format for Value {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         match self {
-            Value::Null => Ok(fmt.write_null(writer)?),
-            Value::Bool(b) => Ok(fmt.write_bool(writer, *b)?),
-            Value::Number(num) => num.format(writer, fmt),
-            Value::String(string) => string.format(writer, fmt),
-            Value::Array(array) => format_array(writer, fmt, array),
-            Value::Object(object) => format_object(writer, fmt, object),
+            Value::Null => Ok(fmt.write_null()?),
+            Value::Bool(b) => Ok(fmt.write_bool(*b)?),
+            Value::Number(num) => num.format(fmt),
+            Value::String(string) => string.format(fmt),
+            Value::Array(array) => format_array(fmt, array),
+            Value::Object(object) => format_object(fmt, object),
         }
     }
 }
@@ -137,15 +129,14 @@ impl Format for Value {
 impl private::Sealed for Number {}
 
 impl Format for Number {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         match *self {
-            Number::PosInt(pos) => fmt.write_int(writer, pos)?,
-            Number::NegInt(neg) => fmt.write_int(writer, neg)?,
-            Number::Float(float) => fmt.write_float(writer, float)?,
+            Number::PosInt(pos) => fmt.write_int(pos)?,
+            Number::NegInt(neg) => fmt.write_int(neg)?,
+            Number::Float(float) => fmt.write_float(float)?,
         }
 
         Ok(())
@@ -155,18 +146,17 @@ impl Format for Number {
 impl private::Sealed for ObjectKey {}
 
 impl Format for ObjectKey {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
         match self {
-            ObjectKey::Identifier(ident) => ident.format(writer, fmt),
-            ObjectKey::String(string) => string.format(writer, fmt),
+            ObjectKey::Identifier(ident) => ident.format(fmt),
+            ObjectKey::String(string) => string.format(fmt),
             ObjectKey::RawExpression(raw) => {
-                fmt.begin_interpolated_string(writer)?;
-                raw.format(writer, fmt)?;
-                fmt.end_interpolated_string(writer)?;
+                fmt.begin_interpolated_string()?;
+                raw.format(fmt)?;
+                fmt.end_interpolated_string()?;
                 Ok(())
             }
         }
@@ -176,12 +166,11 @@ impl Format for ObjectKey {
 impl private::Sealed for RawExpression {}
 
 impl Format for RawExpression {
-    fn format<W, F>(&self, writer: &mut W, _: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
-        writer.write_all(self.as_str().as_bytes())?;
+        fmt.write_all(self.as_str().as_bytes())?;
         Ok(())
     }
 }
@@ -189,12 +178,11 @@ impl Format for RawExpression {
 impl private::Sealed for Identifier {}
 
 impl Format for Identifier {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
-        fmt.write_ident(writer, self.as_str())?;
+        fmt.write_ident(self.as_str())?;
         Ok(())
     }
 }
@@ -202,51 +190,48 @@ impl Format for Identifier {
 impl private::Sealed for String {}
 
 impl Format for String {
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format,
+        W: io::Write,
     {
-        fmt.write_quoted_string(writer, self)?;
+        fmt.write_quoted_string(self)?;
         Ok(())
     }
 }
 
-fn format_array<W, F, T>(writer: &mut W, fmt: &mut F, array: &[T]) -> Result<()>
+fn format_array<W, T>(fmt: &mut Formatter<W>, array: &[T]) -> Result<()>
 where
-    W: ?Sized + io::Write,
-    F: ?Sized + ser::Format,
+    W: io::Write,
     T: Format,
 {
-    fmt.begin_array(writer)?;
+    fmt.begin_array()?;
 
     for value in array {
-        fmt.begin_array_value(writer)?;
-        value.format(writer, fmt)?;
-        fmt.end_array_value(writer)?;
+        fmt.begin_array_value()?;
+        value.format(fmt)?;
+        fmt.end_array_value()?;
     }
 
-    fmt.end_array(writer)?;
+    fmt.end_array()?;
     Ok(())
 }
 
-fn format_object<W, F, K, V>(writer: &mut W, fmt: &mut F, object: &Map<K, V>) -> Result<()>
+fn format_object<W, K, V>(fmt: &mut Formatter<W>, object: &Map<K, V>) -> Result<()>
 where
-    W: ?Sized + io::Write,
-    F: ?Sized + ser::Format,
+    W: io::Write,
     K: Format,
     V: Format,
 {
-    fmt.begin_object(writer)?;
+    fmt.begin_object()?;
 
     for (key, value) in object {
-        fmt.begin_object_key(writer)?;
-        key.format(writer, fmt)?;
-        fmt.begin_object_value(writer)?;
-        value.format(writer, fmt)?;
-        fmt.end_object_value(writer)?;
+        fmt.begin_object_key()?;
+        key.format(fmt)?;
+        fmt.begin_object_value()?;
+        value.format(fmt)?;
+        fmt.end_object_value()?;
     }
 
-    fmt.end_object(writer)?;
+    fmt.end_object()?;
     Ok(())
 }
diff --git a/src/format/mod.rs b/src/format/mod.rs
index 85f6eb0..4e890fa 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -1,13 +1,47 @@
-//! Contains the logic to format HCL data structure.
-
-// @NOTE(mohmann): This module is not exported yet since it is subject to change due to a bigger
-// multi-step refactoring. It will eventually replace the formatting code inside serializer
-// module.
+//! Format data structures as HCL.
+//!
+//! This module provides the [`Formatter`] type and the convienince functions [`to_string`],
+//! [`to_vec`] and [`to_writer`] for formatting the data structures provided by this crate as HCL.
+//!
+//! For serialization of other Rust data structures implementing [`serde::Serialize`] refer to the
+//! documentation of the [`ser`](crate::ser) module.
+//!
+//! ## Examples
+//!
+//! Format an HCL block as string:
+//!
+//! ```
+//! # use std::error::Error;
+//! #
+//! # fn main() -> Result<(), Box<dyn Error>> {
+//! let block = hcl::Block::builder("user")
+//!     .add_label("johndoe")
+//!     .add_attribute(("age", 34))
+//!     .add_attribute(("email", "johndoe@example.com"))
+//!     .build();
+//!
+//! let expected = r#"
+//! user "johndoe" {
+//!   age = 34
+//!   email = "johndoe@example.com"
+//! }
+//! "#.trim_start();
+//!
+//! let formatted = hcl::format::to_string(&block)?;
+//!
+//! assert_eq!(formatted, expected);
+//! #   Ok(())
+//! # }
+//! ```
 
+mod escape;
 mod impls;
 
-use crate::{ser, Result};
-use std::io;
+use self::escape::{CharEscape, ESCAPE};
+use crate::Result;
+use std::io::{self, Write};
+use std::marker::PhantomData;
+use unicode_ident::{is_xid_continue, is_xid_start};
 
 mod private {
     pub trait Sealed {}
@@ -22,8 +56,479 @@ pub trait Format: private::Sealed {
     /// ## Errors
     ///
     /// Formatting the data structure or writing to the writer may fail with an `Error`.
-    fn format<W, F>(&self, writer: &mut W, fmt: &mut F) -> Result<()>
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
     where
-        W: ?Sized + io::Write,
-        F: ?Sized + ser::Format;
+        W: io::Write;
+}
+
+#[derive(PartialEq)]
+enum FormatState {
+    Initial,
+    AttributeStart,
+    AttributeEnd,
+    BlockStart,
+    BlockEnd,
+    BlockBodyStart,
+}
+
+struct FormatConfig<'a> {
+    indent: &'a [u8],
+    dense: bool,
+}
+
+impl<'a> Default for FormatConfig<'a> {
+    fn default() -> Self {
+        FormatConfig {
+            indent: b"  ",
+            dense: false,
+        }
+    }
+}
+
+/// A pretty printing HCL formatter.
+///
+/// ## Examples
+///
+/// Format an HCL block as string:
+///
+/// ```
+/// # use std::error::Error;
+/// #
+/// # fn main() -> Result<(), Box<dyn Error>> {
+/// use hcl::format::{Format, Formatter};
+///
+/// let mut buf = Vec::new();
+/// let mut formatter = Formatter::new(&mut buf);
+///
+/// let block = hcl::Block::builder("user")
+///     .add_label("johndoe")
+///     .add_attribute(("age", 34))
+///     .add_attribute(("email", "johndoe@example.com"))
+///     .build();
+///
+/// block.format(&mut formatter)?;
+///
+/// let expected = r#"
+/// user "johndoe" {
+///   age = 34
+///   email = "johndoe@example.com"
+/// }
+/// "#.trim_start();
+///
+/// let formatted = String::from_utf8(buf)?;
+///
+/// assert_eq!(formatted, expected);
+/// #   Ok(())
+/// # }
+/// ```
+///
+/// The [`builder()`](Formatter::builder) method can be used to construct a custom `Formatter` for
+/// use with a [`Serializer`][Serializer]:
+///
+/// ```
+/// use hcl::{format::Formatter, ser::Serializer};
+/// # let mut writer = Vec::new();
+///
+/// let formatter = Formatter::builder()
+///     .indent(b"  ")
+///     .dense(false)
+///     .build(&mut writer);
+///
+/// let ser = Serializer::with_formatter(formatter);
+/// ```
+///
+/// [Serializer]: ../ser/struct.Serializer.html
+pub struct Formatter<'a, W> {
+    writer: W,
+    config: FormatConfig<'a>,
+    state: FormatState,
+    first_element: bool,
+    current_indent: usize,
+    has_value: bool,
+}
+
+/// A builder to create a `Formatter`.
+///
+/// See the documentation of [`Formatter`] for a usage example.
+pub struct FormatterBuilder<'a, W> {
+    config: FormatConfig<'a>,
+    marker: PhantomData<W>,
+}
+
+impl<'a, W> FormatterBuilder<'a, W> {
+    /// Set the indent for indenting nested HCL structures.
+    pub fn indent(mut self, indent: &'a [u8]) -> Self {
+        self.config.indent = indent;
+        self
+    }
+
+    /// If set, blocks are not visually separated by empty lines from attributes and adjacent
+    /// blocks.
+    pub fn dense(mut self, yes: bool) -> Self {
+        self.config.dense = yes;
+        self
+    }
+
+    /// Consumes the `FormatterBuilder` and turns it into a `Formatter` which writes HCL to the
+    /// provided writer.
+    pub fn build(self, writer: W) -> Formatter<'a, W> {
+        Formatter {
+            writer,
+            config: self.config,
+            state: FormatState::Initial,
+            first_element: false,
+            current_indent: 0,
+            has_value: false,
+        }
+    }
+}
+
+// Public API.
+impl<'a, W> Formatter<'a, W> {
+    /// Creates a new `Formatter` which writes HCL to the provided writer.
+    pub fn new(writer: W) -> Formatter<'a, W> {
+        Formatter::builder().build(writer)
+    }
+
+    /// Creates a new [`FormatterBuilder`] to start building a new `Formatter`.
+    pub fn builder() -> FormatterBuilder<'a, W> {
+        FormatterBuilder {
+            config: FormatConfig::default(),
+            marker: PhantomData,
+        }
+    }
+
+    /// Consumes `self` and returns the wrapped writer.
+    pub fn into_inner(self) -> W {
+        self.writer
+    }
+}
+
+// Internal formatter API.
+impl<'a, W> Formatter<'a, W>
+where
+    W: io::Write,
+{
+    /// Writes `null` to the writer.
+    fn write_null(&mut self) -> io::Result<()> {
+        self.write_all(b"null")
+    }
+
+    /// Writes a boolean value to the writer.
+    fn write_bool(&mut self, value: bool) -> io::Result<()> {
+        let s = if value {
+            b"true" as &[u8]
+        } else {
+            b"false" as &[u8]
+        };
+        self.write_all(s)
+    }
+
+    /// Writes an integer value to the writer.
+    fn write_int<T>(&mut self, value: T) -> io::Result<()>
+    where
+        T: itoa::Integer,
+    {
+        let mut buffer = itoa::Buffer::new();
+        let s = buffer.format(value);
+        self.write_all(s.as_bytes())
+    }
+
+    /// Writes a float value to the writer.
+    fn write_float<T>(&mut self, value: T) -> io::Result<()>
+    where
+        T: ryu::Float,
+    {
+        let mut buffer = ryu::Buffer::new();
+        let s = buffer.format_finite(value);
+        self.write_all(s.as_bytes())
+    }
+
+    /// Writes a quoted string to the writer. The quoted string will be escaped.
+    fn write_quoted_string(&mut self, s: &str) -> io::Result<()> {
+        self.write_all(b"\"")?;
+        self.write_escaped_string(s)?;
+        self.write_all(b"\"")
+    }
+
+    /// Writes a string fragment to the writer. No escaping occurs.
+    fn write_string_fragment(&mut self, s: &str) -> io::Result<()> {
+        self.write_all(s.as_bytes())
+    }
+
+    /// Writes an identifier to the writer. Ensures that `ident` is valid according to the [Unicode
+    /// Standard Annex #31][unicode-standard] before writing it to the writer.
+    ///
+    /// [unicode-standard]: http://www.unicode.org/reports/tr31/
+    fn write_ident(&mut self, ident: &str) -> io::Result<()> {
+        if ident.is_empty() {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                "identifiers must not be empty",
+            ));
+        }
+
+        let mut chars = ident.chars();
+        let first = chars.next().unwrap();
+
+        if !is_xid_start(first) || !chars.all(is_xid_continue) {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                "invalid identifier",
+            ));
+        }
+
+        self.write_string_fragment(ident)
+    }
+
+    /// Writes a string to the writer and escapes control characters and quotes that might be
+    /// contained in it.
+    fn write_escaped_string(&mut self, value: &str) -> io::Result<()> {
+        let bytes = value.as_bytes();
+
+        let mut start = 0;
+
+        for (i, &byte) in bytes.iter().enumerate() {
+            let escape = ESCAPE[byte as usize];
+            if escape == 0 {
+                continue;
+            }
+
+            if start < i {
+                self.write_string_fragment(&value[start..i])?;
+            }
+
+            let char_escape = CharEscape::from_escape_table(escape, byte);
+            char_escape.write_escaped(&mut self.writer)?;
+
+            start = i + 1;
+        }
+
+        if start != bytes.len() {
+            self.write_string_fragment(&value[start..])?;
+        }
+
+        Ok(())
+    }
+
+    /// Starts an interpolated string.
+    fn begin_interpolated_string(&mut self) -> io::Result<()> {
+        self.write_all(b"\"${")
+    }
+
+    /// Ends an interpolated string.
+    fn end_interpolated_string(&mut self) -> io::Result<()> {
+        self.write_all(b"}\"")
+    }
+
+    /// Signals the start of an array to the formatter.
+    fn begin_array(&mut self) -> io::Result<()> {
+        self.current_indent += 1;
+        self.has_value = false;
+        self.first_element = true;
+        self.write_all(b"[")
+    }
+
+    /// Signals the start of an array value to the formatter.
+    fn begin_array_value(&mut self) -> io::Result<()> {
+        if self.first_element {
+            self.first_element = false;
+            self.write_all(b"\n")?;
+        } else {
+            self.write_all(b",\n")?;
+        }
+
+        self.write_indent(self.current_indent, self.config.indent)
+    }
+
+    /// Signals the end of an array value to the formatter.
+    fn end_array_value(&mut self) -> io::Result<()> {
+        self.has_value = true;
+        Ok(())
+    }
+
+    /// Signals the end of an array to the formatter.
+    fn end_array(&mut self) -> io::Result<()> {
+        self.current_indent -= 1;
+
+        if self.has_value {
+            self.write_all(b"\n")?;
+            self.write_indent(self.current_indent, self.config.indent)?;
+        }
+
+        self.write_all(b"]")
+    }
+
+    /// Signals the start of an object to the formatter.
+    fn begin_object(&mut self) -> io::Result<()> {
+        self.current_indent += 1;
+        self.has_value = false;
+        self.write_all(b"{")
+    }
+
+    /// Signals the start of an object key to the formatter.
+    fn begin_object_key(&mut self) -> io::Result<()> {
+        self.write_all(b"\n")?;
+        self.write_indent(self.current_indent, self.config.indent)
+    }
+
+    /// Signals the start of an object value to the formatter.
+    fn begin_object_value(&mut self) -> io::Result<()> {
+        self.write_all(b" = ")
+    }
+
+    /// Signals the end of an object value to the formatter.
+    fn end_object_value(&mut self) -> io::Result<()> {
+        self.end_array_value()
+    }
+
+    /// Signals the end of an object to the formatter.
+    fn end_object(&mut self) -> io::Result<()> {
+        self.current_indent -= 1;
+
+        if self.has_value {
+            self.write_all(b"\n")?;
+            self.write_indent(self.current_indent, self.config.indent)?;
+        }
+
+        self.write_all(b"}")
+    }
+
+    /// Signals the start of an attribute to the formatter.
+    fn begin_attribute(&mut self) -> io::Result<()> {
+        self.maybe_write_newline(FormatState::AttributeStart)?;
+        self.write_indent(self.current_indent, self.config.indent)
+    }
+
+    /// Signals the start of an attribute value to the formatter.
+    fn begin_attribute_value(&mut self) -> io::Result<()> {
+        self.write_all(b" = ")
+    }
+
+    /// Signals the end of an attribute to the formatter.
+    fn end_attribute(&mut self) -> io::Result<()> {
+        self.state = FormatState::AttributeEnd;
+        self.write_all(b"\n")
+    }
+
+    /// Signals the start of a block to the formatter.
+    fn begin_block(&mut self) -> io::Result<()> {
+        self.maybe_write_newline(FormatState::BlockStart)?;
+        self.write_indent(self.current_indent, self.config.indent)
+    }
+
+    /// Signals the start of a block body to the formatter.
+    fn begin_block_body(&mut self) -> io::Result<()> {
+        self.current_indent += 1;
+        self.state = FormatState::BlockBodyStart;
+        self.write_all(b" {")
+    }
+
+    /// Signals the end of a block to the formatter.
+    fn end_block(&mut self) -> io::Result<()> {
+        self.state = FormatState::BlockEnd;
+        self.current_indent -= 1;
+        self.write_indent(self.current_indent, self.config.indent)?;
+        self.write_all(b"}\n")
+    }
+
+    // Conditionally writes a newline character depending on the formatter configuration and the
+    // current and next state. Updates the state to `next_state`.
+    fn maybe_write_newline(&mut self, next_state: FormatState) -> io::Result<()> {
+        let newline = match &self.state {
+            FormatState::AttributeEnd if !self.config.dense => {
+                matches!(next_state, FormatState::BlockStart)
+            }
+            FormatState::BlockEnd if !self.config.dense => {
+                matches!(
+                    next_state,
+                    FormatState::BlockStart | FormatState::AttributeStart
+                )
+            }
+            other => matches!(other, FormatState::BlockBodyStart),
+        };
+
+        if newline {
+            self.write_all(b"\n")?;
+        }
+
+        self.state = next_state;
+        Ok(())
+    }
+
+    fn write_indent(&mut self, n: usize, s: &[u8]) -> io::Result<()> {
+        for _ in 0..n {
+            self.write_all(s)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl<'a, W> io::Write for Formatter<'a, W>
+where
+    W: io::Write,
+{
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.writer.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.writer.flush()
+    }
+}
+
+/// Format the given value as an HCL byte vector.
+///
+/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
+/// [`hcl::to_vec`](crate::to_vec) instead.
+///
+/// # Errors
+///
+/// Formatting fails if the value contains strings that cannot be used as valid HCL identifiers.
+pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
+where
+    T: ?Sized + Format,
+{
+    let mut vec = Vec::with_capacity(128);
+    to_writer(&mut vec, value)?;
+    Ok(vec)
+}
+
+/// Format the given value as an HCL string.
+///
+/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
+/// [`hcl::to_string`](crate::to_string) instead.
+///
+/// # Errors
+///
+/// Formatting fails if the value contains strings that cannot be used as valid HCL identifiers.
+pub fn to_string<T>(value: &T) -> Result<String>
+where
+    T: ?Sized + Format,
+{
+    let vec = to_vec(value)?;
+    let string = unsafe {
+        // We do not emit invalid UTF-8.
+        String::from_utf8_unchecked(vec)
+    };
+    Ok(string)
+}
+
+/// Format the given value as HCL into the IO stream.
+///
+/// If you need to serialize custom data structures implementing [`serde::Serialize`] use
+/// [`hcl::to_writer`](crate::to_writer) instead.
+///
+/// # Errors
+///
+/// Formatting fails if any operation on the writer fails or if the value contains strings that
+/// cannot be used as valid HCL identifiers.
+pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
+where
+    W: io::Write,
+    T: ?Sized + Format,
+{
+    let mut formatter = Formatter::new(writer);
+    value.format(&mut formatter)
 }
diff --git a/src/lib.rs b/src/lib.rs
index 910892e..597427d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,7 +6,7 @@ pub mod de;
 pub mod error;
 #[macro_use]
 mod macros;
-mod format;
+pub mod format;
 mod number;
 mod parser;
 pub mod ser;
diff --git a/src/ser/format.rs b/src/ser/format.rs
deleted file mode 100644
index 3b23b39..0000000
--- a/src/ser/format.rs
+++ /dev/null
@@ -1,491 +0,0 @@
-//! Formatter implementation used by the `Serializer` to construct HCL documents.
-
-use super::escape::{CharEscape, ESCAPE};
-use std::io;
-use unicode_ident::{is_xid_continue, is_xid_start};
-
-mod private {
-    pub trait Sealed {}
-}
-
-/// Abstracts away serializing the HCL control characters.
-///
-/// This trait is sealed to prevent implementation outside of this crate as its specifics might
-/// change in a breaking way.
-pub trait Format: private::Sealed {
-    /// Writes `null` to the writer.
-    fn write_null<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b"null")
-    }
-
-    /// Writes a boolean value to the writer.
-    fn write_bool<W>(&mut self, writer: &mut W, value: bool) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        let s = if value {
-            b"true" as &[u8]
-        } else {
-            b"false" as &[u8]
-        };
-        writer.write_all(s)
-    }
-
-    /// Writes an integer value to the writer.
-    fn write_int<W, I>(&mut self, writer: &mut W, value: I) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-        I: itoa::Integer,
-    {
-        let mut buffer = itoa::Buffer::new();
-        let s = buffer.format(value);
-        writer.write_all(s.as_bytes())
-    }
-
-    /// Writes a float value to the writer.
-    fn write_float<W, F>(&mut self, writer: &mut W, value: F) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-        F: ryu::Float,
-    {
-        let mut buffer = ryu::Buffer::new();
-        let s = buffer.format_finite(value);
-        writer.write_all(s.as_bytes())
-    }
-
-    /// Writes a quoted string to the writer. The quoted string will be escaped.
-    fn write_quoted_string<W>(&mut self, writer: &mut W, s: &str) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b"\"")?;
-        self.write_escaped_string(writer, s)?;
-        writer.write_all(b"\"")
-    }
-
-    /// Writes a string fragment to the writer. No escaping occurs.
-    fn write_string_fragment<W>(&mut self, writer: &mut W, s: &str) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(s.as_bytes())
-    }
-
-    /// Writes an identifier to the writer. Ensures that `ident` is valid according to the [Unicode
-    /// Standard Annex #31][unicode-standard] before writing it to the writer.
-    ///
-    /// [unicode-standard]: http://www.unicode.org/reports/tr31/
-    fn write_ident<W>(&mut self, writer: &mut W, ident: &str) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        if ident.is_empty() {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidData,
-                "identifiers must not be empty",
-            ));
-        }
-
-        let mut chars = ident.chars();
-        let first = chars.next().unwrap();
-
-        if !is_xid_start(first) || !chars.all(is_xid_continue) {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidData,
-                "invalid identifier",
-            ));
-        }
-
-        self.write_string_fragment(writer, ident)
-    }
-
-    /// Writes a string to the writer and escapes control characters and quotes that might be
-    /// contained in it.
-    fn write_escaped_string<W>(&mut self, writer: &mut W, value: &str) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        let bytes = value.as_bytes();
-
-        let mut start = 0;
-
-        for (i, &byte) in bytes.iter().enumerate() {
-            let escape = ESCAPE[byte as usize];
-            if escape == 0 {
-                continue;
-            }
-
-            if start < i {
-                self.write_string_fragment(writer, &value[start..i])?;
-            }
-
-            let char_escape = CharEscape::from_escape_table(escape, byte);
-            char_escape.write_escaped(writer)?;
-
-            start = i + 1;
-        }
-
-        if start != bytes.len() {
-            self.write_string_fragment(writer, &value[start..])?;
-        }
-
-        Ok(())
-    }
-
-    /// Starts an interpolated string.
-    fn begin_interpolated_string<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b"\"${")
-    }
-
-    /// Ends an interpolated string.
-    fn end_interpolated_string<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b"}\"")
-    }
-
-    /// Signals the start of an array to the formatter.
-    fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of an array value to the formatter.
-    fn begin_array_value<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the end of an array value to the formatter.
-    fn end_array_value<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the end of an array to the formatter.
-    fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of an object to the formatter.
-    fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of an object key to the formatter.
-    fn begin_object_key<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of an object value to the formatter.
-    fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b" = ")
-    }
-
-    /// Signals the end of an object value to the formatter.
-    fn end_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.end_array_value(writer)
-    }
-
-    /// Signals the end of an object to the formatter.
-    fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of an attribute to the formatter.
-    fn begin_attribute<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of an attribute value to the formatter.
-    fn begin_attribute_value<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b" = ")
-    }
-
-    /// Signals the end of an attribute to the formatter.
-    fn end_attribute<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of a block to the formatter.
-    fn begin_block<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the start of a block body to the formatter.
-    fn begin_block_body<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-
-    /// Signals the end of a block to the formatter.
-    fn end_block<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write;
-}
-
-#[derive(PartialEq)]
-enum FormatState {
-    Initial,
-    AttributeStart,
-    AttributeEnd,
-    BlockStart,
-    BlockEnd,
-    BlockBodyStart,
-}
-
-/// A pretty printing HCL formatter.
-///
-/// ## Example
-///
-/// The `.builder()` method can be used to construct a `PrettyFormatter` for use with a
-/// [`Serializer`][Serializer]:
-///
-/// ```
-/// use hcl::ser::{PrettyFormatter, Serializer};
-/// # let mut writer = Vec::new();
-///
-/// let formatter = PrettyFormatter::builder()
-///     .indent(b"  ")
-///     .dense(false)
-///     .build();
-///
-/// let ser = Serializer::with_formatter(&mut writer, formatter);
-/// ```
-///
-/// [Serializer]: ser/struct.Serializer.html
-pub struct PrettyFormatter<'a> {
-    state: FormatState,
-    first_element: bool,
-    current_indent: usize,
-    has_value: bool,
-    indent: &'a [u8],
-    dense: bool,
-}
-
-impl<'a> Default for PrettyFormatter<'a> {
-    fn default() -> Self {
-        PrettyFormatter::builder().build()
-    }
-}
-
-/// A builder to create a `PrettyFormatter`.
-///
-/// See the documentation of [`PrettyFormatter`] for a usage example.
-pub struct PrettyFormatterBuilder<'a> {
-    indent: &'a [u8],
-    dense: bool,
-}
-
-impl<'a> PrettyFormatterBuilder<'a> {
-    /// Set the indent for indenting nested HCL structures.
-    pub fn indent(mut self, indent: &'a [u8]) -> Self {
-        self.indent = indent;
-        self
-    }
-
-    /// If set, blocks are not visually separated by empty lines from attributes and adjacent
-    /// blocks.
-    pub fn dense(mut self, yes: bool) -> Self {
-        self.dense = yes;
-        self
-    }
-
-    /// Consumes the `PrettyFormatterBuilder` and turns it into a `PrettyFormatter`.
-    pub fn build(self) -> PrettyFormatter<'a> {
-        PrettyFormatter {
-            state: FormatState::Initial,
-            first_element: false,
-            current_indent: 0,
-            has_value: false,
-            indent: self.indent,
-            dense: self.dense,
-        }
-    }
-}
-
-impl<'a> PrettyFormatter<'a> {
-    /// Creates a new [`PrettyFormatterBuilder`] to start building a new `PrettyFormatter`.
-    pub fn builder() -> PrettyFormatterBuilder<'a> {
-        PrettyFormatterBuilder {
-            indent: b"  ",
-            dense: false,
-        }
-    }
-}
-
-impl<'a> private::Sealed for PrettyFormatter<'a> {}
-
-impl<'a> Format for PrettyFormatter<'a> {
-    fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.current_indent += 1;
-        self.has_value = false;
-        self.first_element = true;
-        writer.write_all(b"[")
-    }
-
-    fn begin_array_value<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        if self.first_element {
-            self.first_element = false;
-            writer.write_all(b"\n")?;
-        } else {
-            writer.write_all(b",\n")?;
-        }
-
-        indent(writer, self.current_indent, self.indent)
-    }
-
-    fn end_array_value<W>(&mut self, _writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.has_value = true;
-        Ok(())
-    }
-
-    fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.current_indent -= 1;
-
-        if self.has_value {
-            writer.write_all(b"\n")?;
-            indent(writer, self.current_indent, self.indent)?;
-        }
-
-        writer.write_all(b"]")
-    }
-
-    fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.current_indent += 1;
-        self.has_value = false;
-        writer.write_all(b"{")
-    }
-
-    fn begin_object_key<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        writer.write_all(b"\n")?;
-        indent(writer, self.current_indent, self.indent)
-    }
-
-    fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.current_indent -= 1;
-
-        if self.has_value {
-            writer.write_all(b"\n")?;
-            indent(writer, self.current_indent, self.indent)?;
-        }
-
-        writer.write_all(b"}")
-    }
-
-    fn begin_attribute<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.maybe_write_newline(writer, FormatState::AttributeStart)?;
-        indent(writer, self.current_indent, self.indent)
-    }
-
-    fn end_attribute<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.state = FormatState::AttributeEnd;
-        writer.write_all(b"\n")
-    }
-
-    fn begin_block<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.maybe_write_newline(writer, FormatState::BlockStart)?;
-        indent(writer, self.current_indent, self.indent)
-    }
-
-    fn begin_block_body<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.current_indent += 1;
-        self.state = FormatState::BlockBodyStart;
-        writer.write_all(b" {")
-    }
-
-    fn end_block<W>(&mut self, writer: &mut W) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        self.state = FormatState::BlockEnd;
-        self.current_indent -= 1;
-        indent(writer, self.current_indent, self.indent)?;
-        writer.write_all(b"}\n")
-    }
-}
-
-impl<'a> PrettyFormatter<'a> {
-    // Conditionally writes a newline character depending on the formatter configuration and the
-    // current and next state. Updates the state to `next_state`.
-    fn maybe_write_newline<W>(&mut self, writer: &mut W, next_state: FormatState) -> io::Result<()>
-    where
-        W: ?Sized + io::Write,
-    {
-        let newline = match &self.state {
-            FormatState::AttributeEnd if !self.dense => {
-                matches!(next_state, FormatState::BlockStart)
-            }
-            FormatState::BlockEnd if !self.dense => {
-                matches!(
-                    next_state,
-                    FormatState::BlockStart | FormatState::AttributeStart
-                )
-            }
-            other => matches!(other, FormatState::BlockBodyStart),
-        };
-
-        if newline {
-            writer.write_all(b"\n")?;
-        }
-
-        self.state = next_state;
-        Ok(())
-    }
-}
-
-fn indent<W>(writer: &mut W, n: usize, s: &[u8]) -> io::Result<()>
-where
-    W: ?Sized + io::Write,
-{
-    for _ in 0..n {
-        writer.write_all(s)?;
-    }
-
-    Ok(())
-}
diff --git a/src/ser/mod.rs b/src/ser/mod.rs
index 17b39cd..3283255 100644
--- a/src/ser/mod.rs
+++ b/src/ser/mod.rs
@@ -3,6 +3,10 @@
 //! This module provides the [`Serializer`] type and the convienince functions [`to_string`],
 //! [`to_vec`] and [`to_writer`] for serializing data to HCL.
 //!
+//! If you want to serialize the data structures provided by this crate (e.g.
+//! [`Body`](crate::Body)) consider using the functionality in the [`format`](crate::format) module
+//! instead because it is more efficient.
+//!
 //! ## Supported top-level types
 //!
 //! The [`Serializer`] supports serialization to HCL for types that are either structured like
@@ -179,20 +183,18 @@
 //! "#
 //! .trim_start();
 //!
-//! let serialized = hcl::to_string(&body).unwrap();
+//! let serialized = hcl::to_string(&body)?;
 //!
 //! assert_eq!(serialized, expected);
 //! #   Ok(())
 //! # }
 //! ```
 
-mod escape;
-mod format;
 #[cfg(test)]
 mod tests;
 
-pub use self::format::{Format, PrettyFormatter, PrettyFormatterBuilder};
 use crate::{
+    format::{Format, Formatter},
     structure::ser::body::{
         BodySerializer, SerializeBodyMap, SerializeBodySeq, SerializeBodyStruct,
         SerializeBodyStructVariant, SerializeBodyTupleVariant,
@@ -203,60 +205,52 @@ use serde::ser::{self, Serialize};
 use std::io;
 
 /// A structure for serializing Rust values into HCL.
-pub struct Serializer<W, F> {
-    writer: W,
-    formatter: F,
+pub struct Serializer<'a, W> {
+    formatter: Formatter<'a, W>,
 }
 
-impl<'a, W> Serializer<W, PrettyFormatter<'a>>
+impl<'a, W> Serializer<'a, W>
 where
     W: io::Write,
 {
-    /// Creates a new `Serializer` which serializes to the provides writer.
-    pub fn new(writer: W) -> Self {
-        Serializer::with_formatter(writer, PrettyFormatter::default())
+    /// Creates a new `Serializer` which serializes to the provides writer using the default
+    /// formatter.
+    pub fn new(writer: W) -> Serializer<'a, W> {
+        Serializer::with_formatter(Formatter::new(writer))
     }
-}
 
-impl<W, F> Serializer<W, F>
-where
-    W: io::Write,
-    F: Format,
-{
-    /// Creates a new `Serializer` which serializes to the provides writer using the provides
-    /// formatter.
-    pub fn with_formatter(writer: W, formatter: F) -> Serializer<W, F> {
-        Serializer { writer, formatter }
+    /// Creates a new `Serializer` which uses the provides formatter to format the serialized HCL.
+    pub fn with_formatter(formatter: Formatter<'a, W>) -> Serializer<'a, W> {
+        Serializer { formatter }
     }
 
     /// Consumes `self` and returns the wrapped writer.
     pub fn into_inner(self) -> W {
-        self.writer
+        self.formatter.into_inner()
     }
 
     fn format<V>(&mut self, value: V) -> Result<()>
     where
-        V: crate::format::Format,
+        V: Format,
     {
-        value.format(&mut self.writer, &mut self.formatter)
+        value.format(&mut self.formatter)
     }
 }
 
-impl<'a, W, F> ser::Serializer for &'a mut Serializer<W, F>
+impl<'a, W> ser::Serializer for &'a mut Serializer<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
 
-    type SerializeSeq = SerializeSeq<'a, W, F>;
-    type SerializeTuple = SerializeSeq<'a, W, F>;
-    type SerializeTupleStruct = SerializeSeq<'a, W, F>;
-    type SerializeTupleVariant = SerializeTupleVariant<'a, W, F>;
-    type SerializeMap = SerializeMap<'a, W, F>;
-    type SerializeStruct = SerializeStruct<'a, W, F>;
-    type SerializeStructVariant = SerializeStructVariant<'a, W, F>;
+    type SerializeSeq = SerializeSeq<'a, W>;
+    type SerializeTuple = SerializeSeq<'a, W>;
+    type SerializeTupleStruct = SerializeSeq<'a, W>;
+    type SerializeTupleVariant = SerializeTupleVariant<'a, W>;
+    type SerializeMap = SerializeMap<'a, W>;
+    type SerializeStruct = SerializeStruct<'a, W>;
+    type SerializeStructVariant = SerializeStructVariant<'a, W>;
 
     serialize_unsupported! {
         bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64
@@ -352,15 +346,14 @@ where
 }
 
 #[doc(hidden)]
-pub struct SerializeSeq<'a, W, F> {
+pub struct SerializeSeq<'a, W> {
     inner: SerializeBodySeq,
-    ser: &'a mut Serializer<W, F>,
+    ser: &'a mut Serializer<'a, W>,
 }
 
-impl<'a, W, F> ser::SerializeSeq for SerializeSeq<'a, W, F>
+impl<'a, W> ser::SerializeSeq for SerializeSeq<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -377,10 +370,9 @@ where
     }
 }
 
-impl<'a, W, F> ser::SerializeTuple for SerializeSeq<'a, W, F>
+impl<'a, W> ser::SerializeTuple for SerializeSeq<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -397,10 +389,9 @@ where
     }
 }
 
-impl<'a, W, F> ser::SerializeTupleStruct for SerializeSeq<'a, W, F>
+impl<'a, W> ser::SerializeTupleStruct for SerializeSeq<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -418,15 +409,14 @@ where
 }
 
 #[doc(hidden)]
-pub struct SerializeTupleVariant<'a, W, F> {
+pub struct SerializeTupleVariant<'a, W> {
     inner: SerializeBodyTupleVariant,
-    ser: &'a mut Serializer<W, F>,
+    ser: &'a mut Serializer<'a, W>,
 }
 
-impl<'a, W, F> ser::SerializeTupleVariant for SerializeTupleVariant<'a, W, F>
+impl<'a, W> ser::SerializeTupleVariant for SerializeTupleVariant<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -444,15 +434,14 @@ where
 }
 
 #[doc(hidden)]
-pub struct SerializeMap<'a, W, F> {
+pub struct SerializeMap<'a, W> {
     inner: SerializeBodyMap,
-    ser: &'a mut Serializer<W, F>,
+    ser: &'a mut Serializer<'a, W>,
 }
 
-impl<'a, W, F> ser::SerializeMap for SerializeMap<'a, W, F>
+impl<'a, W> ser::SerializeMap for SerializeMap<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -477,15 +466,14 @@ where
 }
 
 #[doc(hidden)]
-pub struct SerializeStructVariant<'a, W, F> {
+pub struct SerializeStructVariant<'a, W> {
     inner: SerializeBodyStructVariant,
-    ser: &'a mut Serializer<W, F>,
+    ser: &'a mut Serializer<'a, W>,
 }
 
-impl<'a, W, F> ser::SerializeStructVariant for SerializeStructVariant<'a, W, F>
+impl<'a, W> ser::SerializeStructVariant for SerializeStructVariant<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -503,15 +491,14 @@ where
 }
 
 #[doc(hidden)]
-pub struct SerializeStruct<'a, W, F> {
+pub struct SerializeStruct<'a, W> {
     inner: SerializeBodyStruct,
-    ser: &'a mut Serializer<W, F>,
+    ser: &'a mut Serializer<'a, W>,
 }
 
-impl<'a, W, F> ser::SerializeStruct for SerializeStruct<'a, W, F>
+impl<'a, W> ser::SerializeStruct for SerializeStruct<'a, W>
 where
     W: io::Write,
-    F: Format,
 {
     type Ok = ();
     type Error = Error;
@@ -530,6 +517,10 @@ where
 
 /// Serialize the given value as an HCL byte vector.
 ///
+/// If you want to serialize the data structures provided by this crate (e.g.
+/// [`Body`](crate::Body)) consider using [`hcl::format::to_vec`](crate::format::to_vec) instead
+/// because it is more efficient.
+///
 /// # Errors
 ///
 /// Serialization fails if the type cannot be represented as HCL.
@@ -544,6 +535,10 @@ where
 
 /// Serialize the given value as an HCL string.
 ///
+/// If you want to serialize the data structures provided by this crate (e.g.
+/// [`Body`](crate::Body)) consider using [`hcl::format::to_string`](crate::format::to_string)
+/// instead because it is more efficient.
+///
 /// # Errors
 ///
 /// Serialization fails if the type cannot be represented as HCL.
@@ -561,6 +556,10 @@ where
 
 /// Serialize the given value as HCL into the IO stream.
 ///
+/// If you want to serialize the data structures provided by this crate (e.g.
+/// [`Body`](crate::Body)) consider using [`hcl::format::to_writer`](crate::format::to_writer)
+/// instead because it is more efficient.
+///
 /// # Errors
 ///
 /// Serialization fails if any operation on the writer fails or if the type cannot be represented


Test patch:
diff --git a/src/ser/tests.rs b/src/ser/tests.rs
index b4f4002..7fa4b32 100644
--- a/src/ser/tests.rs
+++ b/src/ser/tests.rs
@@ -240,12 +240,12 @@ qux {
 
     assert_eq!(to_string(&body).unwrap(), default_expected);
 
-    let formatter = PrettyFormatter::builder()
+    let mut buf = Vec::new();
+    let formatter = Formatter::builder()
         .indent(b"    ")
         .dense(true)
-        .build();
-    let mut buf = Vec::new();
-    let mut ser = Serializer::with_formatter(&mut buf, formatter);
+        .build(&mut buf);
+    let mut ser = Serializer::with_formatter(formatter);
     body.serialize(&mut ser).unwrap();
 
     assert_eq!(String::from_utf8(buf).unwrap(), custom_expected);