Skip to main content

Set Operations

Sets are specification-only collections for membership reasoning. They model mathematical sets with union, intersection, and subset operations, useful for tracking permissions, visited states, or unique identifiers.

The core Set module is a ghost API over Sector9's trusted primitive collection model. Sets are proof data, not runtime containers. When runtime code needs a collection, use a runtime module and expose the proof shape through that module's Spec API.

Creating Sets

Create a Set with Set.empty<T>() or Set.singleton(element):

set-basic.sr9
// Creating Sets
persistent actor {
public func check() : async () {
ghost {
// Empty set
let empty : Set<Nat> = Set.empty<Nat>();
assert Set.card(empty) == 0;

// Singleton set
let single = Set.singleton(42);
assert Set.card(single) == 1;
assert Set.contains(42, single);
};
};
}

Sets are typed with element type T. Elements must be spec-immutable types, except opaque/token handles by identity.

Core Operations

Set.empty<T>()

Creates an empty Set of type Set<T>:

let empty : Set<Nat> = Set.empty<Nat>();
assert Set.card(empty) == 0;

Set.singleton(e)

Creates a Set containing exactly one element:

let s = Set.singleton(42);
assert Set.card(s) == 1;
assert Set.contains(42, s);

Set.contains(e, s)

Returns true if element e is in Set s:

let s = Set.singleton(1);
assert Set.contains(1, s); // true
assert not Set.contains(2, s); // true

Note the argument order: element first, then Set.

Set.card(s)

Returns the cardinality (number of elements) in the Set as an Int:

let s = Set.union(Set.singleton(1), Set.singleton(2));
assert Set.card(s) == 2;

Set.union(s1, s2)

Combines elements from both Sets:

set-union.sr9
// Set union combines elements from both sets
persistent actor {
public func check() : async () {
ghost {
let a = Set.singleton(1);
let b = Set.singleton(2);
let combined = Set.union(a, b);

assert Set.contains(1, combined); // from a
assert Set.contains(2, combined); // from b
assert Set.card(combined) == 2;
};
};
}

Union returns a new Set containing elements from both inputs. The input Sets remain usable afterward. See Set Algebra for the algebraic properties.

Set.intersect(s1, s2)

Returns elements present in both Sets:

set-intersect.sr9
// Set intersection finds common elements
persistent actor {
public func check() : async () {
ghost {
let a = Set.union(Set.singleton(1), Set.singleton(2));
let b = Set.union(Set.singleton(2), Set.singleton(3));
let common = Set.intersect(a, b);

assert Set.contains(2, common); // in both
assert not Set.contains(1, common); // only in a
assert not Set.contains(3, common); // only in b
};
};
}

Set.minus(s1, s2)

Returns elements in s1 that are not in s2 (set difference):

set-minus.sr9
// Set difference removes elements
persistent actor {
public func check() : async () {
ghost {
let all = Set.union(Set.singleton(1), Set.singleton(2));
let toRemove = Set.singleton(2);
let result = Set.minus(all, toRemove);

assert Set.contains(1, result); // kept
assert not Set.contains(2, result); // removed
};
};
}

Set.subset(s1, s2)

Returns true if all elements of s1 are in s2:

set-subset.sr9
// Subset relation
persistent actor {
public func check() : async () {
ghost {
let small = Set.singleton(1);
let big = Set.union(Set.singleton(1), Set.singleton(2));

assert Set.subset(small, big); // small is subset of big
assert not Set.subset(big, small); // big is not subset of small

// Empty set is subset of everything
let empty = Set.empty<Int>();
assert Set.subset(empty, small);
};
};
}

The empty Set is a subset of all Sets.

Set Algebra

The trusted primitive set model follows standard mathematical set semantics. Use these laws as proof vocabulary, and keep larger algebraic arguments in small lemmas when a contract starts becoming hard to read.

Union:

  • Commutative: Set.union(a, b) equals Set.union(b, a)
  • Associative: Set.union(a, Set.union(b, c)) equals Set.union(Set.union(a, b), c)
  • Idempotent: Set.union(a, a) equals a

Intersection:

  • Commutative and associative
  • Set.intersect(a, Set.empty()) equals Set.empty()

Difference:

  • Set.minus(a, a) equals Set.empty()
  • Set.minus(a, Set.empty()) equals a

Value Semantics

Set operations that produce new Sets (union, intersect, minus) are ghost, side-effect-free operations: they return persistent values and leave their inputs usable. Read-only operations (contains, card, subset) can also be repeated freely:

ghost {
let s = Set.singleton(1);
let c1 = Set.contains(1, s); // s still valid
let c2 = Set.contains(2, s); // s still valid
let size = Set.card(s); // s still valid
};

Ghost Set Fields

Declare ghost Set fields at actor level to track membership state:

set-ghost-field.sr9
// Ghost Set fields track membership
persistent actor {
ghost var approved : Set<Nat> = Set.empty();

invariant Set.card(approved) >= 0;

public func approve(id : Nat) : async ()
modifies approved
ensures Set.contains(id, approved);
{
ghost {
approved := Set.union(approved, Set.singleton(id));
};
};

public func revoke(id : Nat) : async ()
modifies approved
ensures not Set.contains(id, approved);
{
ghost {
approved := Set.minus(approved, Set.singleton(id));
};
};
}

Ghost Sets are updated inside ghost { } blocks using assignment. Include ghost Set fields in modifies clauses.

Using Sets in Invariants

Sets work well in actor invariants for membership properties:

persistent actor {
ghost var admins : Set<Nat> = Set.singleton(0);

invariant Set.card(admins) >= 1; // Always at least one admin

public func addAdmin(p : Nat) : async ()
modifies admins
ensures Set.contains(p, admins)
{
ghost {
admins := Set.union(admins, Set.singleton(p));
};
};
}

Quantifiers with Sets

Combine Sets with quantifiers for expressive specifications:

set-quantifiers.sr9
// Using quantifiers with Sets
persistent actor {
public func check() : async () {
ghost {
let s = Set.union(Set.singleton(1), Set.singleton(2));

// All elements in s are positive
assert forall<Int>(pure func (x : Int) : Bool =
Set.contains(x, s) ==> x > 0);

// There exists an element equal to 2
assert exists<Int>(pure func (x : Int) : Bool =
Set.contains(x, s) and x == 2);
};
};
}

Common patterns:

  • Use forall with a Set.contains guard to express properties of all members.
  • Use exists to assert at least one element satisfies a condition.
  • Combine Set.contains with ==> for conditional membership properties.

Sets with Map.domain

Sets integrate with Maps through the Map.domain operation:

ghost {
let m = Map.update(Map.empty<Nat, Int>(), 1, 100);
let keys = Map.domain(m); // Returns Set<Nat>

assert forall<Nat>(pure func (k : Nat) : Bool =
Set.contains(k, keys) ==> Map.contains(k, m));
};

Element Type Restrictions

Set elements must be spec-immutable types. The following are rejected:

  • Mutable records (with var fields)
  • Mutable arrays ([var T])
  • Functions (T -> U)
  • Actors and modules
  • Async types

Opaque/token handles may be set elements by identity. The set stores the handle, not a snapshot of its owner payload.

set-immutable-reject_reject.sr9
// Set element types must be spec-immutable
persistent actor {
type MutableCell = { var value : Int }; // Has var field

public func check() : async () {
ghost {
// ERROR: Mutable record cannot be Set element
let s : Set<MutableCell> = Set.empty();
};
};
}

Allowed types include primitives (Nat, Int, Bool, Text), immutable records, tuples, variants, and options with immutable element types.

Operation Reference

OperationSignatureMutates/Consumes InputDescription
Set.empty<T>()Set<T>-Create empty Set
Set.singleton(e)Set<T>-Create single-element Set
Set.contains(e, s)BoolNoCheck membership
Set.card(s)IntNoCount of elements
Set.union(s1, s2)Set<T>NoCombine elements
Set.intersect(s1, s2)Set<T>NoCommon elements
Set.minus(s1, s2)Set<T>NoDifference (s1 - s2)
Set.subset(s1, s2)BoolNoCheck subset relation

Common Patterns

Permission Tracking

persistent actor {
ghost var permissions : Set<Nat> = Set.empty();

public func grant(user : Nat) : async ()
modifies permissions
ensures Set.contains(user, permissions);
{
ghost {
permissions := Set.union(permissions, Set.singleton(user));
};
};

public func revoke(user : Nat) : async ()
modifies permissions
ensures not Set.contains(user, permissions);
{
ghost {
permissions := Set.minus(permissions, Set.singleton(user));
};
};
}

Visited State Tracking

ghost {
var visited : Set<Nat> = Set.empty();
let id : Nat = 42;

assert not Set.contains(id, visited);
visited := Set.union(visited, Set.singleton(id));
assert Set.contains(id, visited);
}

Unique Identifiers

ghost var used : Set<Nat> = Set.empty();

public func allocate(id : Nat) : async Bool
modifies used
ensures result == not Set.contains(id, old(used))
ensures result ==> Set.contains(id, used)
{
if (Set.contains(id, used)) {
false
} else {
ghost {
used := Set.union(used, Set.singleton(id));
};
true
}
}

Summary

  • Sets are specification-only membership collections
  • Set operations are ghost proof operations over a trusted primitive collection model
  • Create with Set.empty<T>() or Set.singleton(element)
  • Query with Set.contains, Set.card, Set.subset
  • Combine with Set.union, Set.intersect, Set.minus
  • Set algebra operations return new Sets; inputs remain usable
  • Elements must be spec-immutable (no mutable fields, arrays, or functions), except opaque/token handles by identity
  • Use ghost Set fields with modifies clauses for actor-level tracking
  • Combine with quantifiers and Map.domain for expressive invariants