lattice_counters/g_counter
A grow-only counter (G-Counter) CRDT.
Each replica maintains its own monotonically increasing count. The global value is the sum across all replicas. Merge takes the pairwise maximum of each replica’s count, guaranteeing convergence.
G-Counter is opaque: use the provided functions to interact with it.
pn_counter in the same package can access internal fields directly.
Example
import lattice_core/replica_id
import lattice_counters/g_counter
let a = g_counter.new(replica_id.new("node-a")) |> g_counter.increment(3)
let b = g_counter.new(replica_id.new("node-b")) |> g_counter.increment(5)
let merged = g_counter.merge(a, b)
g_counter.value(merged) // -> 8
Types
A grow-only counter that tracks per-replica counts.
Each replica identified by a ReplicaId maintains its own count.
The global value is the sum of all per-replica counts.
pub opaque type GCounter
pub type IncrementError {
NegativeDelta(Int)
}
Constructors
-
NegativeDelta(Int)
Values
pub fn from_json(
json_string: String,
) -> Result(GCounter, json.DecodeError)
Decode a G-Counter from a JSON string produced by to_json.
Returns Ok(GCounter) on success, or Error(json.DecodeError) if the
input is not a valid G-Counter JSON envelope.
pub fn increment(counter: GCounter, delta: Int) -> GCounter
Increment the counter by delta.
Adds delta to this replica’s count. delta should be a non-negative
integer; passing a negative value will decrease the local count, which
violates the grow-only invariant and may cause incorrect merge results.
See increment_with_delta for the delta-state variant that also returns
a small payload suitable for incremental sync (e.g. over websockets).
pub fn increment_with_delta(
counter: GCounter,
delta: Int,
) -> #(GCounter, GCounter)
Increment the counter by delta and return both the new state and a delta.
The returned delta is itself a GCounter containing only this replica’s
new count. Merging the delta into a remote replica via merge produces
the same result as merging the full new state — but the delta is a
minimal payload suitable for incremental sync (e.g., over websockets).
See try_increment_with_delta for an error-safe variant. delta should
be non-negative; a negative value will panic.
pub fn merge(a: GCounter, b: GCounter) -> GCounter
Merge two G-Counters using pairwise maximum.
For each replica, the merged count is the maximum of the two inputs.
The result’s self_id is taken from a.
This operation is commutative, associative, and idempotent, satisfying the CRDT join-semilattice laws. Any ordering of concurrent merges will produce the same final state.
pub fn new(replica_id: replica_id.ReplicaId) -> GCounter
Create a new G-Counter for the given replica.
Returns a fresh counter where all per-replica counts are zero.
The replica_id identifies this node and is used when incrementing.
pub fn to_json(counter: GCounter) -> json.Json
Encode a G-Counter as a self-describing JSON value.
Produces an envelope with type, v (schema version), and state.
Format: {"type": "g_counter", "v": 1, "state": {"self_id": "...", "counts": {...}}}
Use from_json to decode the result back into a GCounter.
pub fn try_increment(
counter: GCounter,
delta: Int,
) -> Result(GCounter, IncrementError)
Safely increment the counter by delta.
Returns Error(NegativeDelta(delta)) if delta is negative.
See try_increment_with_delta for the delta-state variant.
pub fn try_increment_with_delta(
counter: GCounter,
delta: Int,
) -> Result(#(GCounter, GCounter), IncrementError)
Safely increment the counter by delta, returning the new state and a delta.
Returns Error(NegativeDelta(delta)) if delta is negative. On success,
returns Ok(#(new_state, delta)) where delta is a GCounter containing
only the changed self-replica entry.