nstree
Utilities to construct branched "namespace strings" for recursive subcomponents, efficiently and flexibly. This is often useful for multiplexing output from processes and other components of a program - e.g. nested dependent services.
This can be used in custom systems, and it may be useful when creating custom log::log! targets or tracing-like nested span structures for when tracing is not quite suitable (or even in cooperation with tracing via custom targets).
Namespaces
This crate primarily operates upon the notion of a namespace path, which consists of "::"-prefixed namespace components. For example:
- The namespace path
"africa::tunisia"is a namespace path consisting of the components"africa"and"tunisia"in that order. - The namespace path
"::africa::tunisia"is semantically identical to the above namespace path, consisting of the components"africa"and"tunisia", in that order. - Importantly, however, the
"::"-prefix does not create an empty namespace component before itself. It can, however, have one after itself. For example, the namespace path"::::africa::tunisia"consists of the 3 components""(an empty component),"africa", and"tunisia". "::"pairs are grouped from left to right, so":::africa:::tunisia::tunis"would be a namespace path with components":africa",":tunisia","tunis". It's not recommended to do things like this, but there is an unambiguous decoding of such strings.
These are constructed in combinator style, similar to Rust iterators, by composing sequences of such components. It's possible (and indeed may be desirable) to construct custom types implementing specific sequences.
Basic Examples
Simple rendering of two "forked" paths:
use ;
let base_path = "europe::uk".by_ref_cache;
let london = base_path.join;
let birmingham = base_path.join;
assert_eq!;
assert_eq!;
Chaining of paths of different structures (also see notes on arrays/vecs/slices/join-macro):
use NamespacePath;
let root = ;
let subprocess = join!;
let full_namespace_path = root.join;
assert_eq!;
Custom sub-sequence/sub-namespace-path:
use ;
// The easiest way to do something like this is to just defer to some static stuff.
// If you need to allocate or do custom formatting, consider implementing
// `IntoNamespacePath` instead, though the idea is pretty similar ^.^
Custom sub-sections of a namespace path (one or more components):
use ;
use IteratorExt as _;
use ;
// RawNamespacePath lets you directly perform `fmt::Display` operations without
// allocations for the whole strings required when generating sequenceso of
// `nstree::NamespaceComponent` dynamically.
//
// However, this makes it harder to do intermediate caching, and results in more
// complex iterator internals, potentially making code less performant. It also makes it
// far more difficult to analyse sequences of components for custom behaviour, and should
// you intend to allocate at some point, it provides less information for making it
// efficient. Not only this, but it cannot guaruntee that implementations do proper
// `"::"`-separation of the iterated components.
//
// All implementations of `NamespacePath` automatically provide a method to convert into a
// RawNamespacePath using the implementation, though you can always implement both -
// or implement `IntoNamespacePath`.
//
// This could be implemented (arguably more cleanly) using `IntoRawNamespacePath`. The only
// problem is that specifying combinators can become unweildy due to lack of ability to
// refer to the output of a function returning `impl Trait`
// (see: <https://rust-lang.github.io/rfcs/3654-return-type-notation.html>
// and <https://rust-lang.github.io/rfcs/2071-impl-trait-type-alias.html>)
// and/or some existential types.
// Rendering, both by RawNamespacePath and NamespacePath
let root = "my-program";
let init_process = ProcessInstance ;
let special_process = ProcessInstance ;
let unnamed_ephemeral = ProcessInstance ;
// RawNamespacePath
let init_process_log = root.raw_join;
// NamespacePath
let special_process_log = root.join;
let unnamed_ephemeral_log = root.join;
// RawNamespacePath::raw_render
assert_eq!;
// NamespacePath::render
assert_eq!;
assert_eq!;
Note on Arrays/Vecs/Slices
Arrays, vectors, and slices all function to combine a sequence of NamespacePaths and RawNamespacePaths into a single combined form of the respective trait. All things written here about NamespacePath also apply to the RawNamespacePath trait. This also applies to the nstree::join! macro.
This means, for instance, that ["a::b", "c::d"] is a NamespacePath consisting of the components of "c::d" concatenated onto the components of "a::b". In most cases this is intuitive.
However, if a member of the array/slice/vec (from now on we will just call these arrays) is a NamespacePath with no components - for example, "" (the empty string) - then there will be no separator between it and any components afterwards. That is, the array ["a::b", "", "c::d"] is an entirely equivalent NamespacePath to the array ["a::b", "c::d"] - there is no extra "empty" component between "b" and "c", like might be expected if you had written ["a", "b", "", "c", "d"] or if you'd received some parameter that sometimes could have an entirely empty/"zero-component" NamespacePath as a part of your own constructions.
If you want to guarantee that there is always at least one component in some sub-sequence of an outputted NamespacePath, there are several options:
- Apply a non-zero-component prefix (or suffix or both) unconditionally to each sub-collection that you wish to guarantee has some notation - such that it always appears even if the original sequence definition may sometimes have no components - using
NamespacePath::join - Apply a fallback default either to an entire subsection or some part of it, such that when that part of the namespace path has no components, it is replaced with some default sequence such as
"::"(you could also do multi-level fallbacks with customisation).
This would be done withNamespacePath::fallback - Perform a combination of these.
Available Crate Features
This crate provides 2 features:
alloc(default enabled) - when enabled, this provides functions that produce dynamically allocated stdlib types (such asStringandVec), as well as implementations ofNamespacePathandRawNamespacePathon them and their combinations. It makes this library dependent on theallocstandard library crate.std(default enabled) - when enabled, this makes the library depend on the full Rust Standard Library (std) - it provides some comparison implementations, and it also implies thatallocis enabled.
By default, alloc and std are both enabled - you can disable them by using default-features=false inside your Cargo.toml