Crate lignin[][src]

Expand description

lignin, named after the structural polymer found in plants, is a lightweight but comprehensive VDOM data type library for use in a wider web context.

About the Documentation

DOM API terms are bold italic and linked to the MDN Web Docs. (Please file an issue if this isn’t the case somewhere.)

Implementation Contract

This is not a soundness contract. Code using this crate must not rely on it for soundness. However, it is free to panic when encountering an incorrect implementation.


See also the implementation contracts on Node::Text::text, Node::Comment::comment and Attribute::name.

When rendering the VDOM as HTML text, extra care must be taken to syntactically validate everything according to the specification!

HTML renderers should error rather than panic when encountering a VDOM that they can’t guarantee will be parsed as intended (assuming any syntax errors potentially cause undefined behavior somewhere).
However, renderers are free to be lenient in this regard by adjusting their output to be syntactically valid in a way that’s unlikely to cause a changed user experience. (That is: Feel free to substitute illegal character sequences in comments and such.)


The DOM may contain extra siblings past the nodes mentioned in the VDOM. Renderers must ignore them.

Similarly, the DOM may contain extra attributes and event bindings. Renderers must ignore them unless attributes collide.
Components must clean up extra attributes and event listeners they have previous added to the DOM via the DOM API on teardown.

This simplifies renderers and allows reuse of DOM nodes between components, which in turn reduces the amount of DOM API calls necessary.

See also the implementation contracts on DomRef and Node::Keyed.


While the order of attributes reported by the DOM API in browsers isn’t specified and event listeners can’t be examined this way, components should stick to a relatively consistent order here and place conditional attributes and event bindings past always present ones in the respective slices.

When adding or removing Nodes dynamically between updates, components should wrap lists in Node::Multi and otherwise insert an empty Node::Multi([&[]) as placeholder for an absent element.

Each of these suggestions allows better and easier diff optimization in renderers, but otherwise mustn’t be a strict requirement for compatibility.

Deep Comparisons

All core comparison traits (PartialEq, Eq, PartialOrd and Ord) are implemented recursively where applicable.

Note that CallbackRefs derived from separate instances of CallbackRegistration are still considered distinct, regardless of the receiver and handler used to make them. However, without the "callbacks" feature, all CallbackRef instances are inactive and indistinct.

For shallow comparisons, access and compare fields directly or memoize parts of the GUI.



Enables DOM callback support. Requires std.

Without this feature, most of the callback API is still available but stand-in types in web are vacant and will materialize into any type.

Always test VDOM generators with the "callbacks" feature enabled if they make use of them at all, but only depend on it in order to invoke callbacks.

Notes on Performance


Clone is always implemented via Copy in this crate, since none of the instances provide heap storage.

Comparisons and Hashing

As shallow hashes would easily collide for most applications where VDOM hashing comes up, Hash is implemented recursively in this crate and is potentially expensive. The same applies to PartialEq, Eq, PartialOrd and Ord.

As an exception, Node::Memoized instances are compared only by their state_key. Their content is ignored for comparisons and does not factor into their hash.

lignin does not implement hash caching by itself, so users of HashMap or similar containers should wrap node graphs in a “HashCached<T>” type first.


As lignin targets HTML and DOM rather than XML, it does not support processing instructions or CDATA sections.

For the same reason, there is formally no information about VDOM identity, which could be used to render self-referential XML documents.

In practice, it may be possible to determine identity by comparing pointers, but this would require some workarounds regarding lignin’s slices-of-values to be general.

The implementation itself would be quite error-prone on types that are Copy due to implicit by-value copies there. Proceed with caution if you must!

Element and attribute names are always plain &strs, which isn’t ideal for software that renders its GUI more directly than through a web browser. I’m open to maintaining a generic fork if there’s interest in this regard.

with "callbacks" feature

While the limit is relatively high at u32::MAX, the total number of CallbackRegistrations that can be created over the program’s lifetime is still limited¹.

For this reason, you should hold onto CallbackRegistration instances as long a possible and avoid recreating them for each VDOM update.

However, you must not make assumptions about when the respective callback is invoked in relation to a component being rendered, as CallbackRefs can legally be kept over multiple VDOM updates.

See CallbackRegistration for storage hints.

¹ APIs exist in callback_registry to partially or completely reset this limit, but they have additional safety requirements to the application as a whole.

without "callbacks" feature

While the "callbacks" feature is disabled, all callback management is erased. This makes lignin faster and removes all instantiation limits on CallbackRegistration, but removes unique identities from CallbackRegistration and CallbackRef, which affects comparisons and hashing.

MathML support is rudimentary due lack of direct support in web-sys.

Usage Notes

Optional Tags

This is only a suggestion. Renderers should normally not depend on certain tags to be present or absent.

While HTML allows certain elements, like <body> to be partially or entirely implied, lignin is not granular enough to model this accurately.

Implied elements are also still present in the browser DOM, even if both their start and end tag have been omitted.

As such, these elements should normally be explicit in the VDOM. HTML renderers may omit tags from the serialised document or fragment according to the HTML specification.


pub use callback_registry::CallbackRef;
pub use callback_registry::CallbackRegistration;
pub use web::DomRef;
pub use web::Materialize;



Transitive (across function boundaries) ThreadSafety inference, mainly for use by frameworks.


Callback registry plumbing, for renderers and app runners that support them and need to run indefinitely.


Erasable web type stand-ins used as callback parameters.



Mainly for use by frameworks. Canonically located at auto_safe::AutoSafe_alias.
Creates a custom-visibility alias for auto_safety::AutoSafe.


Canonically located at callback_registry::if_callbacks.
Identity iff the "callbacks" feature is enabled, otherwise empty output.
In most cases, prefer using the ENABLED constant to always check all of your code.


Canonically located at callback_registry::if_not_callbacks.
Identity iff the "callbacks" feature is not enabled, otherwise empty output.
In most cases, prefer using the ENABLED constant to always check all of your code.



Vdom Represents a single HTML Attr with name and value.


Vdom Represents a single HTMLElement as name, attributes, content and event_bindings.


Vdom Maps to options parameter values of Document.createElement() (including undefined) or (currently only) the global is attribute.


Vdom Represents a single DOM event binding with name and callback.


Vdom Maps to options parameter values of EventTarget.addEventListener().


Vdom A VDOM node that has its DOM identity preserved during DOM updates even after being repositioned within a (path-)matching Node::Keyed.


ThreadSafety marker for !Send + !Sync.


ThreadSafety marker for Send + Sync.



Vdom A single generic VDOM node.



Marker trait for thread-safety tokens.


Marker trait for VDOM data types, which (almost) all vary by ThreadSafety.