pub struct Doc<C: Cost = DefaultCost>(/* private fields */);Expand description
An abstract document containing pretty printing instructions.
The Doc contains the tree of instructions and choices to present to the
pretty printer. The printer uses this information to choose an optimal
layout and produce it when asked.
A Doc is internally wrapped in an Rc, so it is cheap to clone. It is
advantageous to clone and reuse a Doc when creating different choices
of layout for the same content, as this will save the printer from repeating
work. See the docs for | for more
details on that.
§Constructing documents
Documents are constructed by starting with small pieces of text, which are then wrapped and composed with various operators to describe constraints on how they should be laid out by the printer.
§Creating documents with content
The two basic atomic units of documents are:
text: Renders some text verbatimnewline: Moves the printer to the next line, possibly with indentation
All of the actual content in a document comes from these two functions or convenience functions that wrap them.
§Wrapping documents to modify behavior
There are also documents that wrap other documents to modify how they are rendered by the printer:
nest: Increases the indentation level for the documentalign: Aligns subsequent lines in the document to its starting columnreset: Resets the indentation level to 0full: Requires the document to take up the remainder of its linecost: Artificially increase the cost of a document
Using these modifiers can help get exactly the desired layout with minimal fuss.
§Combining documents
Documents can be combined to create more complex ones using two operators:
These two operators are what enables the printer to optimally format complex documents. Small documents can be combined into larger ones representing an entire piece of text, and creating choices along the way gives the printer options for different ways to format, allowing it to choose an optimal layout.
§Printing documents
Doc implements Display, so they can be printed
in all the usual ways you expect to be able to print things in Rust. By
default, the doc will be constrained to a page width of 80 characters. If
you’re using one of Rust’s formatting macros,
then you can use a width parameter to customize this.
§What if it fails to render?
Rendering a document can fail. Generally when this happens, it means
that you’ve put too tight of constraints on the choices given, so the
printer isn’t able to find an option that is allowed. If you use Doc’s
implementation of Display with a document that fails
to render, it will panic, as Display doesn’t give a way to surface custom
errors. Usually, this is fine: correctly constructed documents for printing
should always have a valid choice path, even if some of the subdocuments
within do not.
If you want to be able to detect when a document has failed to print,
you can call doc.validate(page_width). This will run
the printer up to the point where it chooses a layout and return either
the layout chosen or an Error. The returned layout also implements
Display, so it can be printed from there. Note that since the page width
was chosen when validate was called (since it was needed for the printer
to choose the layout), the width cannot be customized in format macros when
printing a layout.
§Thread safety
Due to their use of Rc internally, Docs cannot be moved from the thread
where they are created. They also use a thread-local static counter to assign
a unique ID to each Doc, so moving them between threads would also introduce
the possibility of collisions for these IDs.
Implementations§
Source§impl Doc<DefaultCost>
impl Doc<DefaultCost>
Sourcepub fn validate(&self, page_width: usize) -> Result<PrintResult<DefaultCost>>
pub fn validate(&self, page_width: usize) -> Result<PrintResult<DefaultCost>>
Run the printer to produce a layout with the default cost factory.
This can be used instead of printing the document directly to be able to check if the printer failed to find a layout for the document.
If this returns Ok, then the resulting PrintResult can be printed
to produce the chosen layout.
§Example
let doc = text("hello") & space() & text("world");
let result = doc.validate(80)?;
assert_eq!(result.cost(), DefaultCost(0, 0));
assert!(!result.is_tainted());
assert_eq!(result.to_string(), "hello world");
// the only valid layout exceeds the computation width limit
// (6 in this case) so the layout is still produced, but tainted
let result = doc.validate(5)?;
assert_eq!(result.cost(), DefaultCost(36, 0));
assert!(result.is_tainted());
assert_eq!(result.to_string(), "hello world");
let doc = fail();
assert!(matches!(doc.validate(80), Err(pretty_expressive::Error)));
Source§impl<C: Cost> Doc<C>
impl<C: Cost> Doc<C>
Sourcepub fn validate_with_cost<CF: CostFactory<CostType = C> + 'static>(
&self,
cost: CF,
) -> Result<PrintResult<C>>
pub fn validate_with_cost<CF: CostFactory<CostType = C> + 'static>( &self, cost: CF, ) -> Result<PrintResult<C>>
Run the printer to produce a layout with a custom cost factory.
This function behaves just like validate, but you
provide it the entire cost factory rather than the page width.
This is the only way to print documents that use a Cost that is not
DefaultCost.
§Example
let cf = DefaultCostFactory::new(20, Some(40));
let doc = text("hello") & space() & text("world");
let result = doc.validate_with_cost(cf)?;
assert_eq!(result.cost(), DefaultCost(0, 0));
assert!(!result.is_tainted());
assert_eq!(result.to_string(), "hello world");Trait Implementations§
Source§impl<C: Cost> BitAnd for Doc<C>
The & operator is used to concatenate documents.
impl<C: Cost> BitAnd for Doc<C>
The & operator is used to concatenate documents.
This is the fundamental operator for creating a larger document out of smaller ones.
Source§impl<C: Cost> BitOr for Doc<C>
The | operator is used to create a choice between multiple documents.
impl<C: Cost> BitOr for Doc<C>
The | operator is used to create a choice between multiple documents.
This is the fundamental operator for giving the printer options for different ways to layout a document.
Source§fn bitor(self, rhs: Doc<C>) -> Self::Output
fn bitor(self, rhs: Doc<C>) -> Self::Output
Creates a document that will be rendered to one of two documents.
The printer will determine which side of the choice is prettier and will render using that layout.
A choice should generally include the same content, but with different formatting. That said, there is no restriction on what appears on either side of a choice: they can be completely different if that is what serves your needs.
§Example
let doc = text("hello world") |
(text("hello") & hard_nl() & text("world"));
assert_eq!(doc.to_string(), "hello world");§Sharing subdocuments
The printer is able to take advantage of the fact that the tree of choices for a document isn’t actually a tree: it’s a DAG (directed acyclic graph). Because much of the content on either side of the choice is the same, the printer can reuse calculations if it knows which subdocuments are equivalent. Finding the optimal layout for the same document at the same column and indentation level will always give the same result, but the printer might reach that through many different paths through the choice graph.
To cache computed results, each Doc is assigned an ID internally.
Docs are reference-counted (using Rc) internally, so when you
clone() a Doc, you get another reference to the same document, with
the same ID. For this reason, cloning documents to reuse on different
sides of a choice is ideal. So this:
let hello = text("hello");
let world = text("world");
let doc = v_append(hello.clone(), world.clone()) |
us_append(hello.clone(), world.clone());is always preferable to this:
let doc = v_append(text("hello"), text("world")) |
us_append(text("hello"), text("world"));Even if they will produce the same result. It may not make a big difference in this small of an example, but in practice, there can be many subdocuments behind each side of a choice, and each subsequent choice compounds that exponentially.
Auto Trait Implementations§
impl<C> Freeze for Doc<C>
impl<C> RefUnwindSafe for Doc<C>where
C: RefUnwindSafe,
impl<C = DefaultCost> !Send for Doc<C>
impl<C = DefaultCost> !Sync for Doc<C>
impl<C> Unpin for Doc<C>
impl<C> UnwindSafe for Doc<C>where
C: RefUnwindSafe,
Blanket Implementations§
§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§unsafe fn clone_to_uninit(&self, dest: *mut u8)
unsafe fn clone_to_uninit(&self, dest: *mut u8)
clone_to_uninit)