Doc

Struct Doc 

Source
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 verbatim
  • newline: 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 document
  • align: Aligns subsequent lines in the document to its starting column
  • reset: Resets the indentation level to 0
  • full: Requires the document to take up the remainder of its line
  • cost: 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:

  • &: Concatenate documents to form a larger one
  • |: Create a choice between two documents

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>

Source

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>

Source

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.

This is the fundamental operator for creating a larger document out of smaller ones.

Source§

type Output = Doc<C>

Concatenation forms a new document.

Source§

fn bitand(self, rhs: Doc<C>) -> Self::Output

Concatenates two documents.

This places the documents one after the other in the layout. No additional spacing or alignment is added.

§Example
let doc = text("hello") & space() & text("world");
assert_eq!(doc.to_string(), "hello world");
Source§

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§

type Output = Doc<C>

A choice between two documents is a new document.

Source§

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.

Source§

impl<C: Cost> Clone for Doc<C>

Source§

fn clone(&self) -> Self

Creates a new reference to the same document.

Docs are internally reference-counted.

1.0.0§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<C: Debug + Cost> Debug for Doc<C>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Doc<DefaultCost>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<T> Borrow<T> for T
where T: ?Sized,

§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
§

impl<T> BorrowMut<T> for T
where T: ?Sized,

§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CloneToUninit for T
where T: Clone,

§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> From<T> for T

§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T, U> Into<U> for T
where U: From<T>,

§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
§

impl<T> ToString for T
where T: Display + ?Sized,

§

fn to_string(&self) -> String

Converts the given value to a String. Read more
§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.