The State and Slot interfaces provide a type-safe approach to managing and transmitting state. The State interface offers an immutable way to collect and chain Slots, each with a name, type, and value. It enforces type safety through method overloading, supporting primitives, strings, names, and states. These interfaces create a foundation for constructing states and communicating changes type-safely and performantly for small sets.
1 2 3 4 5 6 7 8 9 10 11 |
var cortex = cortex (); var XYZ = cortex.name ( "x.y.z" ); var ABC = cortex.name ( "a.b.c" ); var DEF = cortex.name ( "d.e.f" ); var state = cortex .state ( XYZ, 1 ) .state ( ABC, 2 ); |
To resolve the value of a Name within a State, a Slot is created with the type, name, and value. When passed to the State::value method, it matches against the state’s name and exact type. If found, it returns the held slot value; otherwise, it falls back to the specified slot’s value. The following prints 1 to the console.
1 2 3 4 5 6 7 |
var xyz = cortex.slot ( XYZ, -1 ); out.println ( state.value ( xyz ) ); |
To modify an existing value within a State, we can use the state method to create a new State with the overriding named value. The following code will now print 3 to the console.
1 2 3 4 5 6 7 |
state = state.state ( XYZ, 3 ); out.println ( state.value ( xyz ) ); |
A State stores the type with the name, only matching when both are exact matches. The new slot has no impact with 3 printed as before in the following code because the new slot type added is now of type String.
1 2 3 4 5 6 7 8 |
state = state .state ( XYZ, "4" ); out.println ( state.value ( xyz ) ); |
A State does not discard previously added slots. The value method returns the most recently added value that matches the specified Slot. To list all values for a specific slot use the values method, which returns a Stream.
1 2 3 |
state.values ( xyz ).forEach ( out::println ); |
It is also possible to retrieve all slots added to a State using the stream method which returns a Stream of slots.
1 2 3 4 5 6 7 8 9 10 11 12 |
state.stream () .forEach ( slot -> out.printf ( "%s[%s]=%s%n", slot.name (), slot.type ().getSimpleName (), slot.value () ) ); |
Adding the same equivalent slot consecutively will not result in adding a slot. The following prints 343.
1 2 3 4 5 6 7 8 9 |
cortex .state ( XYZ, 3 ) .state ( XYZ, 4 ) .state ( XYZ, 4 ) .state ( XYZ, 3 ) .values ( xyz ) .forEach ( out::print ); |
A compact version of a State can be returned with the compact method. The following prints just 3.
1 2 3 4 5 6 7 8 |
cortex .state ( XYZ, 4 ) .state ( XYZ, 3 ) .compact () .values ( xyz ) .forEach ( out::print ); |
The State interface is used or expected to be used within the Substrates API in two primary places. The first is the state method in the Subject interface, which allows a vendor to expose internal stats of their implementation.
The second is as an emittance, where nesting is typical of data. The following prints unknown and william.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
var NAME = cortex.name ( "name" ); var NODE = cortex.name ( "node" ); state = cortex .state ( NODE, cortex.state ( NAME, "william" ) ); var node = cortex.slot ( NODE, cortex.state () ); var name = cortex.slot ( NAME, "unknown" ); out.println ( state.value ( name ) ); out.println ( state .value ( node ) .value ( name ) ); |
The State and Slot interfaces offer a compelling alternative to traditional attribute systems in observability toolkits like OpenTelemetry. Instead of relying on “stringly-typed” key-value pairs that can lead to runtime errors and type mismatches, this design enforces type safety at compile time through its slot-based approach.
The immutable nature of the State interface, coupled with efficient digest-based lookups, provides a robust foundation for high-performance observability systems. Perhaps most significantly, the design’s support for hierarchical organization through nested states aligns naturally with the structured nature of modern observability data, whether representing spans in distributed traces, fields in log records, or labels in metrics. The State/Slot pattern demonstrates how meticulous API design can make code safer and more maintainable.