lattice_maps/crdt
A tagged union over all leaf CRDT types with dynamic dispatch.
The Crdt type wraps individual CRDTs (counters, registers, sets) so they
can be stored and merged uniformly — this is how ORMap holds heterogeneous
values. For direct use, prefer the individual modules (e.g., g_counter,
or_set) for type-safe access.
Maps (LWWMap, ORMap) are not included in this union to avoid circular
module dependencies.
Example
import lattice_maps/crdt
import lattice_core/replica_id
import lattice_counters/g_counter
let a = crdt.CrdtGCounter(g_counter.new(replica_id.new("node-a")) |> g_counter.increment(1))
let b = crdt.CrdtGCounter(g_counter.new(replica_id.new("node-b")) |> g_counter.increment(2))
let assert Ok(merged) = crdt.merge(a, b)
Types
A tagged union wrapping every leaf CRDT type in this library.
Variants:
CrdtGCounter— grow-only counterCrdtPnCounter— increment/decrement counterCrdtLwwRegister— last-writer-wins register (String)CrdtMvRegister— multi-value register (String)CrdtGSet— grow-only set (String)CrdtTwoPSet— two-phase set (String)CrdtOrSet— observed-remove set (String)CrdtVersionVector— version vector
Parameterized types are fixed to String for v1. Maps (LWWMap,
ORMap) are composite containers and are not included in this union
to avoid circular module dependencies.
pub type Crdt {
CrdtGCounter(g_counter.GCounter)
CrdtPnCounter(pn_counter.PNCounter)
CrdtLwwRegister(lww_register.LWWRegister(String))
CrdtMvRegister(mv_register.MVRegister(String))
CrdtGSet(g_set.GSet(String))
CrdtTwoPSet(two_p_set.TwoPSet(String))
CrdtOrSet(or_set.ORSet(String))
CrdtVersionVector(version_vector.VersionVector)
}
Constructors
-
CrdtGCounter(g_counter.GCounter) -
CrdtPnCounter(pn_counter.PNCounter) -
CrdtLwwRegister(lww_register.LWWRegister(String)) -
CrdtMvRegister(mv_register.MVRegister(String)) -
CrdtGSet(g_set.GSet(String)) -
CrdtTwoPSet(two_p_set.TwoPSet(String)) -
CrdtOrSet(or_set.ORSet(String)) -
CrdtVersionVector(version_vector.VersionVector)
Specifies which leaf CRDT type an ORMap holds as its values.
When or_map.update is called on a key that does not yet exist, the map
uses this spec to auto-create a default value via default_crdt. Choosing
the right spec at or_map.new time is important because changing the
value type after the fact would require migrating all existing values.
pub type CrdtSpec {
GCounterSpec
PnCounterSpec
LwwRegisterSpec
MvRegisterSpec
GSetSpec
TwoPSetSpec
OrSetSpec
}
Constructors
-
GCounterSpec -
PnCounterSpec -
LwwRegisterSpec -
MvRegisterSpec -
GSetSpec -
TwoPSetSpec -
OrSetSpec
Error returned when merging two Crdt values of different types.
The expected and found fields contain human-readable type names
(e.g., "g_counter", "or_set").
pub type MergeError {
TypeMismatch(expected: String, found: String)
}
Constructors
-
TypeMismatch(expected: String, found: String)
Values
pub fn default_crdt(
spec: CrdtSpec,
replica_id: replica_id.ReplicaId,
) -> Crdt
Create a new default (bottom) value of the specified CRDT type.
The replica_id is passed to CRDT constructors that require it
(counters, registers, OR-Set). For types that don’t use a replica
identifier (G-Set, 2P-Set), the argument is ignored.
Default values per spec:
GCounterSpec/PnCounterSpec— new counter forreplica_idLwwRegisterSpec— empty string""at timestamp0forreplica_id(bottom element)MvRegisterSpec— new MV-Register forreplica_idGSetSpec/TwoPSetSpec— empty set (no replica needed)OrSetSpec— new OR-Set forreplica_id
pub fn default_delta(
spec: CrdtSpec,
replica_id: replica_id.ReplicaId,
) -> Crdt
Return an empty/identity delta for the given spec.
An empty delta is the join-semilattice bottom: merging it into any state
returns that state unchanged. ORMap uses this when accumulating deltas
across multiple mutations and as the per-key default when no value-CRDT
change is needed.
For most types this is identical to default_crdt. The exception is
LwwRegisterSpec, where the bottom is the same (value="", timestamp=0)
register; the merge semantics ensure it is dominated by any subsequent
real write at any positive timestamp.
pub fn from_json(
json_string: String,
) -> Result(Crdt, json.DecodeError)
Decode a Crdt from a JSON string produced by to_json.
Reads the "type" field to determine which type-specific decoder to
use. Returns Error if the string is not valid JSON, the "type" field
is missing, or the type tag is not recognized.
pub fn is_empty_delta(
value: Crdt,
spec: CrdtSpec,
replica_id: replica_id.ReplicaId,
) -> Bool
Return True when a wrapped CRDT carries no observable change relative
to the bottom (default) state for the given spec and replica.
Used by ORMap to decide whether a value-CRDT delta is worth packaging
into the surrounding map delta. An empty delta merged into a remote is a
no-op, so emitting it would just waste bandwidth.
Implementation: structural equality against default_delta(spec, rid).
A delta from a no-op mutation may still appear “non-empty” (e.g. a
GCounter carrying {self_id: 0} is structurally distinct from the
fresh empty counter); such cases produce a small but harmless delta.
pub fn matches_spec(value: Crdt, spec: CrdtSpec) -> Bool
Return True when a wrapped CRDT matches the expected CrdtSpec.
pub fn merge(a: Crdt, b: Crdt) -> Result(Crdt, MergeError)
Dispatch merge to the type-specific merge function for matching variants.
If a and b hold the same variant, their inner values are merged using
the type-specific merge function and returned as Ok(merged).
If a and b hold different variants, returns
Error(TypeMismatch(expected: ..., found: ...)) where expected is the
type name of a and found is the type name of b.