Expand description
Read the docs here.
§Overview
Galois connections as first-class Rust values. Use them to cast lawfully
between numeric types, and compose ladders of conversions whose round-trip
behavior is determined by simple inequalities rather than left to chance.
Every operation derived from a Conn (rounding, saturation, median, …)
carries a property-tested invariant. The generated fixed-width
integer, Q-format, NonZero, and iso families also have Kani harnesses
for full bit-width SMT proofs; float SMT coverage is narrower and
called out under Testing → SMT verification.
MSRV: Rust 1.88. Bumps to the MSRV will be treated as minor-version
changes — pin connections = "0.1" and an MSRV upgrade will surface as
a 0.2 release rather than a silent break on a patch update.
This crate is a Rust-native port of the Haskell library
connections.
§Why this crate
Galois connections are the right shape for static, lawful
conversions between partially ordered types (e.g. f64 → f32,
Duration → seconds, f32 → u32 → IpAddr, etc) where each link in
the chain is specifiable at compile time.
The standard cast operators as, From, and Into give you exactly one
direction at a time — and as in particular is silent on rounding,
saturation, and lossy conversion. Three concrete things this crate gives
you that the standard tools don’t:
-
Clear semantics.
(x as f32) as f64 != xfor manyx: f64. With aConn, at least one of the following pairs of inequalities is property-tested for every connection in this crate:- left-Galois:
ceil(a) ≤ b iff a ≤ upper(b) - right-Galois:
lower(b) ≤ a iff b ≤ floor(a)
A
ConnisCopy,const-constructible, heap-free, and the crate is#![forbid(unsafe_code)]. - left-Galois:
-
Safely composable.
The
compose!macro folds a chain of pairwise Conns into one freshConn<Src, Dst>at compile time. A composedConnobeys the same properties as its component connections by construction.
§Quick start
use connections::prelude::*;
use connections::core::B2;
use connections::core::i016::I016BE02;
let bytes = B2([0x01, 0x02]);
assert_eq!(I016BE02.ceil(258_i16), bytes);
assert_eq!(I016BE02.floor(258_i16), bytes);
assert_eq!(I016BE02.upper(bytes), 258_i16);
assert_eq!(I016BE02.lower(bytes), 258_i16);See EXAMPLES.md for a sequence of ten worked examples in various domains.
§What are connections?
A Galois connection
between preorders A and B is a pair of monotone maps f: A → B and
g: B → A such that f(x) ≤ y ⇔ x ≤ g(y). We say f is the left
or lower adjoint, and g is the right or upper adjoint of the
connection.
Here is a simple connection between two 3-element sets:

(image courtesy of 7 Sketches in Compositionality).
Each row is a (a, b) pair; arrows show the action of f (A → B,
bottom legend) and g (B → A, top legend). Lone arrows mark
single-direction maps (f(1) = 1, g(2) = 2); the ↔ marks a
matched pair where both adjoints agree (f(3) = 3, g(3) = 3); the
adjacent ↰ ↳ glyphs depict the lens f(2) ↔ g(1) — two
non-crossing curves between rows 2 and 1, the geometric signature of
adjointness.
§How to use connections
Galois connections compose: (f₁ ⊣ g₁) ∘ (f₂ ⊣ g₂) is again an
adjunction, and compose! builds that composite statically,
law-checked as a whole. The moment you apply a destructor (e.g.
upper , lower, ceil, floor, etc) you’ve left the Conn algebra
and produced a concrete value that can no longer be composed. So early
destructuring throws away the static guarantees that the full
chain would have otherwise enjoyed. Therefore you will get the most
bang for your buck if you follow two heuristics.
Lift through the Conn. A Conn is a little black box: the higher-
arity helpers (e.g. ceil*, floor*, round*, truncate*, etc)
take your arguments, do something with them in the Conn’s other
(usually higher-fidelity) domain, and return the result back in
your original domain. ceil2(t, h, b1, b2) is f(h(g(b1), g(b2))):
embed b1/b2 into the wider domain via g, run the closure h
there, round back via f. Use this for domain arithmetic that would
overflow or lose precision in your own type - do it in the wider
domain and round back. Never hand-roll saturating math.
Compose at the site. Export Conns at the library level and use
the ConnL/ConnR/ConnK API instead of get/set functions. When
client code needs a multi-hop conversion, build the exact Conn with
the compose macros (compose, compose_l, compose_r, compose_k)
statically at the call site. Do not thread intermediates by hand.
If the client code takes a runtime parameter then it’s best to keep the
helper as a normal named function whose body visibly composes with
the lawful Conns it depends on.
The discipline of pushing runtime parameters and conversion policy choices close to the static Conn call site means that the policy and the static cast are both visible in the same body. The results is code that is visibly correct, easy to test, and extensible to future use cases.
§Library
§L & R kind connections
The basic type in this library is:
pub struct Conn<A, B, K: Kind = L> {
f: fn(A) -> B, // L-kind: ceil; R-kind: floor
g: fn(B) -> A, // L-kind: upper, R-kind: lower
// plus a phantom kind tag K ∈ {L, R}
}A Conn<A, B, K> is exactly a Galois connection — a pair of monotone
functions (f, g) whose adjoint role depends on the kind tag. An
L-kind Conn satisfies f(a) ≤ b ⟺ a ≤ g(b); an R-kind Conn
satisfies g(b) ≤ a ⟺ b ≤ f(a).
The kind K = {L, R} determines the API. L/ConnL exposes .ceil()
and .upper(), while R/ConnR exposes .floor() and .lower().
- Direction names —
ceil(rounds up) andfloor(rounds down) — match downstream intuition. “Give me a ceiling cast” doesn’t require the caller to know which side of an adjunction they’re on. However calling.floor()on an L-kind connection, orceilon an R-kind connection results in a compiler error. - Position names —
upper(the upper adjoint of the L-pair) andlower(the lower adjoint of the R-pair) — match the math: a genericT: ConnKbound exposes both because a triple has both adjunctions, regardless of which way each one rounds in any concrete instance. - Consts vs markers - Regular connections are
pub consts of typeConn<A, B, L>orConn<A, B, R>. Two-sidedConnKconnections ship aspub structs — zero-sized marker types implementing bothConnLandConnR. The const-vs-struct shape tells you which kind a name refers to at a glance.
§API
- L-side methods on
Conn<_, _, L>(and on anyConnLimplementor via default-method dispatch):ceil,upper, plusceil1/2,upper1/2lifters. - R-side methods on
Conn<_, _, R>(and on anyConnRimplementor):floor,lower, plusfloor1/2,lower1/2lifters. - Two-sided helpers (re-exported at the crate root):
interval,round/round1/round2,truncate/truncate1/truncate2,median. All bind onT: ConnK(super-trait ofConnL + ConnRover the same(A, B)), so they’re callable only on triple markers — not on one-sided Conns.
Kind discipline is structural: calling .floor(...) on an L-kind
Conn is a compile error (the method only exists on Conn<_, _, R>),
and likewise .ceil(...) on R. Two-sided helpers similarly reject
one-sided operands at compile time because a one-sided Conn doesn’t
implement ConnK.
§Modules
| Family | Module |
|---|---|
| IEEE-754 types | float |
Q-format binary fixed-point (Q###Q###, i8/u8 … i128/u128 backing) | fixed::{i008,…,i128, u008,…,u128} (fixed cargo feature) |
Std-int widening + narrowing + cross-sign (I###I###, U###I###, U###U###, I###U###) | core::{i008,…,i128, u008,…,u128} |
iN/uN ↔ NonZero<{i,u}N> (I###N###, U###N###) | core::{i008,…,i128, u008,…,u128} |
Cross-crate iso Fixed{I,U}<U0> ↔ {i,u}{N} (Q000I###, Q000U###) and signed normalized bit isos (Q007I008 … Q127I128) | fixed::{i008,…,i128, u008,…,u128} (fixed cargo feature) |
Float narrowing f64 ↔ f32 ↔ f16 under N5 (F064F032, F032F016, F064F016) | core::{f032,f064} (f16 cargo feature for f16) |
time crate types (DATEJDAY, TIMENANO, TIMESECS, TDURSECS, F032TDUR, F064TDUR, PDTMDATE, ODTMNANO, ODTMSECS) and the std::time::Duration family (SDURU064, SDURU128, F064SDUR, F032SDUR) for users on std::time | time::{clock,date,datetime,duration,offset} (time cargo feature) |
hifitime nanosecond Duration + Epoch Conns — calendar (MONTU008, WKDYU008), duration (HDURNANO, HDURSECS, F064HDUR), and per-timescale epoch bridges (EUNXNANO, ETAINANO, F064ETAI, EGPSNANO, …) | hifi::{calendar,duration,epoch} (hifi cargo feature) |
uhlc hybrid-logical-clock Conns — NTP64 ↔ u64 (NDURU064) and the HLC ID bridge (HLIDLX16) | uhlc::{ntp64,id} (uhlc cargo feature) |
std::net addresses (U032IPV4, U128IPV6, IPV6IPV4, IPVXIPV4, IPVXIPV6, SOVXSOV4, SOVXSOV6) | addr |
char codepoint projection (U032CHAR, surrogate-gap-aware) | core::char |
Pointer-width usize saturating casts (USZEU008, USZEU016, USZEU032, USZEU064, USZEU128) | core::usize |
Pointer-width isize casts (ISZEI008, ISZEI016, ISZEI128; → i32/→ i64 deferred) | core::isize |
Sortable byte encodings (U008BE01, U008LE01, I008BE01, I008LE01, BOOLBE01, BOOLLE01, through U128BE16, U128LE16, I128BE16, I128LE16) | core::{bool, i008,…,i128, u008,…,u128} |
Constant-name prefixes are letter-disambiguated: Q for Q-format
wrappers (sign and host bit-width come from the module path), I/U
for std primitives (digits = bit-width), N for NonZero<*>, F for
IEEE floats. Cross-module name collisions are allowed and resolved by
qualified import (e.g. fixed::i008::Q008Q000 and
fixed::i064::Q008Q000 co-exist).
§ConnK connections
When the same inner function can serve as both upper and lower
and satisfies an additional order-reflecting property (see
Sandwich inequality), the library combines
the two resulting connections into a zero-sized marker struct that
gains a third group of ‘ambidextrous’ helpers via a super-trait that
ties the L and R sides together:
ConnL— capability trait with associated typestype A: Copy; type B: Copy;and aconn_l()projection to the L-viewConn<A, B, L>. Default methods expose.ceil()and.upper().ConnR— symmetric capability trait whoseconn_r()projects to the R-viewConn<A, B, R>. Default methods expose.floor()and.lower().ConnK— super-traitConnL + ConnRover the same(A, B)pair; the two-sided helpers (round,truncate, …) bind onConnKand reach through both views.
The trait names match the value-type spellings on purpose: a blanket
impl ConnL for Conn<A, B, L> (and the R-side analogue) makes every
one-sided value also satisfy the trait, so a generic T: ConnL bound
accepts triple markers and raw Conn<A, B, L> values uniformly, and
inner is defined as a free function in module scope, referenced
from the marker’s trait impls; no struct in the crate stores three
function pointers.
§Adjoint triples
You construct a ConnK marker out of three functions: ceil, inner,
and floor using one of the crate’s provided macros. Note that both
ceil/inner and inner/floor must satisfy the connection
inequalities given above. In addition ceil/floor must satisfy
the following ‘sandwich’ inequality: for every a, floor(a) ≤ ceil(a)
Triples ceil/inner/floor that satisfy all three properties are known
as adjoint triples — the
ceil ⊣ inner ⊣ floor shape outlined in
Example 3.
The sandwich inequality is equivalent to the earlier requirement that
inner be order-reflecting.
The prop::conn::law_battery! full subset enforces both floor_le_ceil
as well as order_reflecting:
Proof of equivalence is outlined in the following section.
§Sandwich inequality
Both directions of the equivalence follow from applications of the
adjunction laws. Both proofs use only L-Galois f ⊣ g, R-Galois
g ⊣ h, monotonicity, and transitivity — no extra assumptions.
Sufficiency (inner order-reflecting ⟹ floor(a) ≤ ceil(a)).
Take any a ∈ A. The two closure laws give
inner(floor(a)) ≤ a ≤ inner(ceil(a))so by transitivity inner(floor(a)) ≤ inner(ceil(a)). Since inner
is order-reflecting, this lifts to floor(a) ≤ ceil(a). ∎
Necessity (floor(a) ≤ ceil(a) everywhere ⟹ inner order-reflecting).
Take x, y ∈ B with inner(x) ≤ inner(y). Chain:
x ≤ floor(inner(x)) -- kernel of inner ⊣ floor, with b = x
≤ ceil(inner(x)) -- assumption at a = inner(x)
≤ y -- L-Galois ceil(a) ≤ b ⟺ a ≤ inner(b),
with a = inner(x), b = y; the RHS
inner(x) ≤ inner(y) is givenSo x ≤ y. ∎
(Categorically: in an adjoint triple f ⊣ g ⊣ h over posets, g
fully faithful ⟺ the counit of g ⊣ h is iso ⟺ the unit of f ⊣ g
is iso ⟺ h ≤ f. The two displays above are that equivalence written for
posets, where “fully faithful” reduces to “order-reflecting” and “iso” to
“equality”.)
Counterexample — necessity is sharp. Let A = {a} (one element)
and B = {b₁ < b₂ < b₃}, with inner: B → A the constant map
(inner(b) = a for every b — monotone but maximally non-injective).
Watch what the per-side Galois laws force:
- L-Galois
ceil(a) ≤ b ⟺ a ≤ inner(b). The RHS reduces toa ≤ a, which is always true, soceil(a) ≤ bfor everyb ∈ B. The smallest suchbisb₁, soceil(a) = b₁. - R-Galois
inner(b) ≤ a ⟺ b ≤ floor(a). The LHS reduces toa ≤ a, always true, sob ≤ floor(a)for everyb, givingfloor(a) = b₃.
Both per-side adjunctions hold, every monotonicity check passes — and
yet floor(a) = b₃ > b₁ = ceil(a). The “triple” type-checks and the
per-side laws are satisfied, but the rounding sandwich is inverted.
The two-sided helpers inherit the inversion. round(a) compares
inner(floor(a)) = a with inner(ceil(a)) = a to pick the closer
endpoint, finds them equal, and falls through to truncate, which
returns whichever side the source-zero rule selects — a value with no
in-band signal that anything is wrong. A connection that fails the
sandwich inequality isn’t an academic foul; the two-sided helpers
actively misbehave on it.
§Installation
cargo add connections| MSRV | Rust 1.88 (matches rust-toolchain.toml) |
| Edition | 2024 |
| License | MIT (see LICENSE-MIT) |
Optional cargo features:
| Feature | What it enables | Toolchain |
|---|---|---|
fixed | connections::fixed::{i008,…,u128} Q-format ladders, float→Q bridges, Q.0 primitive isos, and signed normalized bit isos | stable |
proptest | Re-exports connections::prop::arb (proptest strategies) for downstream test suites | stable |
macros | Re-exports the internal codegen macro families under connections::macros / crate-root macro paths | stable |
time | Civil-calendar and time-span Conns backed by the time crate | stable |
hifi | Nanosecond-precision hifitime::Duration / Epoch Conns | stable |
f16 | IEEE binary16 connections (F016, F032F016, F064F016) and their proptest strategies | nightly (uses #![feature(f16)] — tracking #116909) |
try_trait | ?-operator (Try / FromResidual) support on Interval, Extended, N5 — extracts the success payload; Interval / Extended short-circuit on boundary variants, N5 is infallible | nightly (uses #![feature(try_trait_v2)] — tracking #84277) |
The connections::prop::conn and connections::prop::lattice
predicate modules are always public — they’re pure bool-returning
functions over this crate’s own types and don’t depend on proptest.
The testing feature only adds prop::arb, the strategy module that
does pull proptest in as a regular dependency.
§Testing
cargo test --workspaceEvery connection runs its proptest law suite on every commit (the
pre-commit hook in .claude/settings.json enforces this). Float
generators are biased toward NaN, ±∞, ±0, denormals, and ULP-boundary
values. Fixed-point generators are biased toward 0, ±PREC, and
±i64::MAX/PREC so saturation boundaries are exercised on every run.
Runtime dependencies are feature-gated. The
fixed crate backs the optional
binary fixed-point ladder, time
backs the optional civil-calendar / clock surface, and
hifitime backs the optional
high-precision time surface. Proptest is a dev-dependency, exposed
publicly behind the testing feature for downstream test suites.
Every connection ships with proptest coverage of the following laws — the
predicates live in prop::conn and are re-runnable by downstream
crates against their own connections:
| Law | Statement |
|---|---|
conn_galois_l | ceil(a) ≤ b ⟺ a ≤ inner(b) |
conn_galois_r | inner(b) ≤ a ⟺ b ≤ floor(a) |
conn_closure_l | a ≤ inner(ceil(a)) (unit) |
conn_closure_r | inner(floor(a)) ≤ a |
conn_kernel_l | ceil(inner(b)) ≤ b (counit) |
conn_kernel_r | b ≤ floor(inner(b)) |
conn_monotone_l | a₁ ≤ a₂ ⟹ ceil(a₁) ≤ ceil(a₂) ∧ floor(a₁) ≤ floor(a₂) |
conn_monotone_r | b₁ ≤ b₂ ⟹ inner(b₁) ≤ inner(b₂) |
conn_idempotent | inner ∘ ceil is idempotent on its image |
A tenth law, conn_floor_le_ceil (floor(a) ≤ ceil(a)), is asserted
only on ConnK connections whose inner is an injective embedding. See
above.
For float-bearing types, the ≤ is an N5 lattice.
In particular, NaN is reflexive, NaN sits between ±∞, and finite values
are strictly ordered. N5 carries these semantics.
§SMT verification (Kani)
Beyond the proptest law suite — which samples — the generated
fixed-width integer / Q-format / NonZero / iso connection families
listed in src/kani.rs
have Kani harnesses for their Galois-law predicates over the full
bit-width domain. The pointer-width
usize / isize families (core::usize / core::isize) are the
exception: CBMC models a single concrete pointer width per run, so
those Conns are covered by the proptest battery on the host target
rather than a width-agnostic SMT proof. The proof tree lives at
src/kani/
and is gated behind #[cfg(kani)] so it compiles only under
cargo kani — release builds, cargo test, and downstream consumers
see no proof code. No new runtime dependency: Kani injects its own
crate at proof time.
The float-specific SMT result is deliberately narrower: the IEEE bit
space is too large for full-Galois proofs to be tractable, so the
f64 → f32 ULP-walk in src/core/f064.rs (ceil_f64_f32 /
floor_f64_f32) is proven to converge in ≤ 2 iterations for every
finite non-NaN f64, not just the proptest sample. Three tiered harnesses
(float_walk::t0_* for the full domain, t1_* for |x| ≤ 1e6,
t2_* for the [1, 2) binade) each verify the bound under
progressively tighter input restrictions. These harnesses do not prove
full float Galois laws over NaN/±∞, and they do not cover the
float→integer or float→fixed macro helper bodies; those branches are
covered by the proptest law batteries and explicit helper/unit tests.
Run with:
cargo install --locked kani-verifier
cargo kani setup
cargo kani # full proof tree
cargo kani --harness 'float_walk::t0_' # the headline
cargo kani --harness 'int_narrow::' # one familyPer-harness wall times are in the milliseconds-to-seconds range; the full tree runs in well under two minutes.
§Naming
Every public Conn constant has an 8-character XnnnYmmm shape:
4 chars per side (smaller-resolution / coarser tier on the right).
Most families take a 1-letter prefix and 3 zero-padded digits;
the time-crate Conns use the all-letter ABCDXYZW shape (e.g.
TDURSECS).
| code | type | meaning |
|---|---|---|
F016 | float::F016 (gated) | IEEE binary16 |
F032 | F032 | IEEE binary32 |
F064 | F064 | IEEE binary64 |
F128 | (deferred — f128 unstable) | IEEE binary128 |
I008 | i8 | signed 8-bit |
I016 | i16 | signed 16-bit |
I032 | i32 | signed 32-bit |
I064 | i64 | signed 64-bit |
I128 | i128 | signed 128-bit |
U008 | u8 | unsigned 8-bit |
U016 | u16 | unsigned 16-bit |
U032 | u32 | unsigned 32-bit |
U064 | u64 | unsigned 64-bit |
U128 | u128 | unsigned 128-bit |
USZE | usize | pointer-width unsigned (all-letter side) |
ISZE | isize | pointer-width signed (all-letter side) |
For binary fixed-point, every Conn whose endpoints are Q-format
wrappers from the fixed crate uses the Q prefix on those
sides. Sign and host bit-width are implicit from the module path
(fixed::i008 → signed 8-bit, fixed::u008 → unsigned 8-bit, etc.);
the 3-digit field is the frac level. Backing width therefore lives
in the module path: fixed::i008::Q008Q004 is the i8-backed 8-frac
→ 4-frac Conn, while fixed::i064::Q008Q004 is the i64-backed
analogue. Both share the constant name Q008Q004; resolution is
by qualified import:
use connections::fixed::i008 as fi008;
use connections::fixed::i064 as fi064;
let _ = fi008::Q008Q000; // i8-backed Q0.8 → Q8.0
let _ = fi064::Q008Q000; // i64-backed Q56.8 → Q64.0Each fixed::iN / fixed::uN submodule also exports a per-host
pub type alias family — for example
fixed::i008::I004 = FixedI8<U4> (signed Q4.4) and
fixed::u016::U008 = FixedU16<U8> (unsigned Q8.8) — for direct use
of the wrapper type without a Conn. The I/U prefix marks host
signedness and the three digits are the frac level — a deliberately
distinct namespace from the Conn-name Q### prefix used in
Q008Q004-style identifiers. The fixed module docs (gated on the
fixed feature) carry the full layering rationale.
Std-int Conn families live under core::{i008,…,u128} (the
pure-core/std half of the crate’s top-level split — see
core). Peer numeric Conns are source-oriented:
I008I016 (signed widening Extended<i8> → i16) lives in
core::i008; I016I008 (signed narrowing i16 → i8) lives in
core::i016; U008I008 (cross-sign non-widening u8 → i8) lives
in core::u008.
The signed-widening (I###I###) and unsigned-into-signed-widening
(U###I###) families wrap the source in
Extended and ship as adjoint-triple markers
(unit structs implementing both ConnL and
ConnR). The other six families ship as
single-sided Conn consts — left-Galois
(Conn::new_l) for the U→U / I→U widening +
the I→I / U→U / I→U narrowing cases, right-Galois
(Conn::new_r) for U→I non-widening (where
the saturation plateau lives on the target side).
Examples:
core::f064::F064F032—F064 → F032(lossy IEEE narrowing).core::f064::F064F016—F064 → F016(direct f64 → IEEE binary16,f16feature).core::f032::F032F016—F032 → F016(f32 → IEEE binary16,f16feature).core::u008::U008U016—u8 → u16saturating widen.core::i008::I008I016—Extended<i8> → i16(signed widening, range-extended source).core::u008::U008I016—Extended<u8> → i16(unsigned source into signed target).core::i016::I016I008—i16 → i8signed-narrowing saturating cast.core::u064::U064U008—u64 → u8unsigned-narrowing saturating cast.core::u008::U008I008—u8 → i8non-widening cross-sign (right-Galois single-sided).core::i016::I016U008—i16 → u8cross-sign narrowing (negative-clip + saturate).fixed::u008::Q008Q007—FixedU8<U8> → FixedU8<U7>(Q0.8 ↔ Q1.7, the 7-bit MIDI velocity format).fixed::u016::Q016Q015—FixedU16<U16> → FixedU16<U15>(Q0.16 ↔ Q1.15, canonical signed-PCM-equivalent unsigned audio amplitude).fixed::u032::Q032Q031—FixedU32<U32> → FixedU32<U31>(Q0.32 ↔ Q1.31, the canonical 32-bit normalised-amplitude format).core::i008::I008N008—i8 → NonZeroI8(asymmetric adjoint at zero:floor(0) = -1,ceil(0) = +1).fixed::i008::Q000I008—FixedI8<U0> ↔ i8cross-crate isofixed::i016::Q015I016— signed normalizedFixedI16<U15> ↔ i16bit iso (Q8.0 lossless bridge to the std primitive).
F128 is blocked on f128 stabilisation in stable Rust.
§Usage
§ConnK
The two-sided helpers (ConnK-bound free fns,
re-exported via prelude) carry the following π-bracket
worked-example thread in their per-fn doctests:
use connections::conn::ConnL;
use connections::float::N5;
use connections::core::f064::F064F032;
let pi64 = N5::new(std::f64::consts::PI);
// f32's nearest representation of π widened losslessly to f64.
let pi32 = N5::new(std::f32::consts::PI as f64);
// Lossless ≠ precise: the value is still the f32 approximation.
assert_ne!(pi64, pi32);
// upper just widens; for F064F032 that's the f32 → f64 cast.
assert_eq!(F064F032.upper(N5::new(std::f32::consts::PI)), pi32);interval— bracket ofxas anInterval<A>(closed cell[lo, hi] ⊆ Asharingx’s B-projection;Interval::Emptyfor NaN-bearing inputs)truncate,truncate1,truncate2— round-toward-zero through the tripleround— round-to-nearest f32 of true π (ties broken toward zero)round1— Newton step onsinnear πround2— catastrophic-cancellation recoverymedian— Birkhoff median (i32 ordered lattice- N5 lattice with NaN, both at the function’s doctest)
Principal-filter / principal-ideal predicates live as inherent
methods on the one-sided views: Conn::filter_l
on Conn<_, _, L> (upward-closed: ceil(a) ≤ b),
Conn::filter_r on Conn<_, _, R>
(downward-closed: b ≤ floor(a)).
§Composition
Conn<A, B, K> stores two bare fn pointers (f
and g) plus a phantom kind tag, so the type is Copy, const-
constructible, and heap-free — which prevents a generic .then()
method (a composed fn would need to capture both inputs, which bare
fn cannot). For the compile-time-known case the
compose_l! / compose_r!
macros expand a chain of two or more same-kind Conn consts into
a fresh Conn<Src, Dst> / Conn<Src, Dst, R>:
use connections::compose_l;
use connections::conn::Conn;
// Three-step compose at the L-side: id ∘ id ∘ id = id.
const ID_I32: Conn<i32, i32> = Conn::identity();
const COMPOSED: Conn<i32, i32> = compose_l!(ID_I32, ID_I32, ID_I32);For triple markers, compose_k! is the
declaration-form combiner: it generates a fresh marker unit-struct
whose ConnL / ConnR impls compose
the parents’ views via compose_l! / compose_r!. The bare
compose! is a forwarder to compose_l!,
mirroring the K = L default of Conn. Runtime composition is
deferred until a closure-capturing DynConn variant lands; see the
repository’s doc/design.md (not shipped in the published crate).
§Codegen macros
Every Conn const in this crate is generated by one of a small
number of macro families. The declaration-form macros
(conn_k! and its forward = back shorthand
iso!, plus the one-sided conn_l!
/ conn_r!) along with
compose_k! / compose_l!
/ compose_r! / compose!
are unconditionally exported. The seven internal saturating-cast /
NonZero macro families (uint_uint!, int_uint!, ext_int!,
*_narrow!, uint_int_sat!, nz_*_ext!) ship under the
connections::macros module, gated on the macros cargo
feature, for downstream crates that want to extend this crate’s
algebra to their own bounded-integer / Q-format / NonZero types.
Modules§
- addr
- Network-address connections.
- conn
- Galois-connection core: the
Conn<A, B, K>type, the kind markersLandR, theConnL/ConnR/ConnKcapability traits for adjoint connections, and the operations on aConn(kind-gated accessors and lifters, plus two-sided rounding helpers onConnK). - core
- Connections among Rust core/std primitive types —
iN/uNstd integers, IEEE floats (f16/f32/f64),bool,char, andcore::num::NonZero<T>. The crate’s only top-level domain split is between this module (Conns whose endpoints all ship incore/std) andcrate::fixed(Conns where at least one endpoint is afixed-crate Q-format wrapper). - extended
Extended<T>wraps a totally-orderedTwithNegInf(bottom) andPosInf(top). Used as the target of connections whose target type cannot represent the full range of the source — e.g. a float source feeding into a bounded integer rung saturates toNegInf/PosInfwhen the finite range is exceeded.- fixed
fixed - Connections among
fixed-crate Q-format types — theFixedI<N><Frac>/FixedU<N><Frac>wrappers and their cross-crate isomorphisms to the matching std primitive integers. Thecore/fixedsplit betweencrate::coreand this module is the crate’s only top-level domain distinction: every Conn whose endpoints all ship incore/stdlives incrate::core; every Conn where at least one endpoint is afixed-crate Q-format wrapper lives here. - float
- IEEE float Conns built on
N5<T>, a transparent wrapper that gives IEEE floats the N5 preorder used by the Galois-connection machinery. - hifi
hifi - Galois connections among
hifitimetypes. - interval
- Order-theoretic intervals: a closed
[lo, hi]interval in a preordered set, with anEmptyvariant covering the incomparable case. - lattice
- Lattice hierarchy.
- macros
macros - Codegen macros for downstream crates building their own
bounded-integer / Q-format / NonZero / iso lattices on top of
this crate. Gated on the
macroscargo feature. - prelude
- Idiomatic batch import of the full
ConnAPI, grouped into three analogous polarity surfaces brought into scope together. - prop
- Property predicates and proptest strategies for testing consumers of this crate’s algebras.
- time
time - Galois connections among time-domain types, spanning both the
timecrate andstd::time. - uhlc
uhlc - uhlc Conns
Macros§
- compose
- Forwarding alias for
compose_l!— mirrors theK = Ldefault ofConn. Use when composing L-side conns and you’d rather not spell the side explicitly. Error spans may point at this forwarder rather thancompose_l!directly. - compose_
k - Declaration-form macro: compose two adjoint-triple markers into a
fresh triple marker. The composed marker is a unit struct with
freshly-built
ConnLandConnRimpls whoseconn_l/conn_rmethods build the projection viacompose_l!/compose_r!over the parents’ views. - compose_
l - Compose a chain of L-Conn paths into a single fresh
Conn<Src, Dst>. - compose_
r - Compose a chain of R-Conn expressions into a single fresh
Conn<Src, Dst, R>. - conn_k
- Declaration-form macro: ship a new adjoint-triple marker from
three free-function paths
(ceil, inner, floor). - conn_l
- Declaration-form macro: ship a one-sided left-Galois
Conn(ceil ⊣ inner) as apub constof typeConn<A, B>. - conn_r
- Declaration-form macro: ship a one-sided right-Galois
Conn(inner ⊣ floor) as apub constof typeConn<A, B, R>. - ext_int
Conn<Extended<$A>, $B>widening with Extended source (left-Galois). Covers bothI??I??(signed widening) andU??I??(unsigned-into-signed widening) — the body is identical, only the source’sMIN/MAXdiffer.- float_
ext_ int - Emit a full adjoint-triple Conn
Conn<N5<$float>, Extended<$int>>(implsConnL+ConnR) viaconn_k!. - float_
ext_ int_ l - Emit an L-only Conn
Conn<N5<$float>, Extended<$int>, L>. - float_
fixed fixed - Emit a full adjoint-triple Conn
Conn<N5<$float>, Extended<$Fixed<$Frac>>>(implsConnL+ConnR) viaconn_k!. - float_
fixed_ l fixed - Emit an L-only Conn
Conn<N5<$float>, Extended<$Fixed<$Frac>>, L>. - int_
int_ narrow Conn<i_N, i_M>narrowing (bits(i_N) > bits(i_M)).- int_
uint Conn<i_N, u_M>widening or same-width cross-sign (bits(i_N) ≤ bits(u_M)).- int_
uint_ narrow Conn<i_N, u_M>narrowing (bits(i_N) > bits(u_M)).- iso
- Declaration-form macro: ship a degenerate-Galois iso (lossless
bijection) from a single
(forward, back)function pair. - law_
battery - Generate a property-test battery for an adjoint connection.
- lift_k
- Declaration-form macro: lift a
ConnKparent (an iso or other triple marker) through theExtendedfunctor, emitting a fresh unit-struct marker that implsConnLandConnRonExtended<A>↔Extended<B>. - lift_l
- Lift an L-Galois
Conn(or anyConnLimpl) through theExtendedfunctor. - lift_r
- Lift an R-Galois
Conn(or anyConnRimpl) through theExtendedfunctor. Mirrorslift_l!: produces a freshConn<Extended<A>, Extended<B>, R>whose synthetic markers map identically and whoseFinitearm dispatches through the parent’slower/floor. - nz_
int_ ext Conn<i<N>, NonZero<i<N>>>— saturateiNontoNonZero<iN>with asymmetric rounding at zero. Source is the bare primitive (notExtended<iN>) so there are no infinities at the source-side plateau to break the Galois law: everyiNvalue other than 0 is itself aNonZero<iN>, and 0 sandwiches betweenNonZero(-1)andNonZero(+1).- nz_
int_ narrow Conn<NonZero<i_N>, NonZero<i_M>>narrowing (bits(i_N) > bits(i_M)).- nz_
uint_ ext Conn<u<N>, NonZero<u<N>>>— unsigned counterpart ofnz_int_ext!.floorandceilcollapse identically at 0 because there is no NonZero ≤ 0 on the unsigned side: both pickNonZero(1), the smallest representable NonZero. Single-sided left-Galois (Conn::new_l) —floor = ceil. The right-Galois law fails at(NonZero(1), 0)(no NonZero strictly below 1 to act as the “previous” rounding target).- nz_
uint_ narrow Conn<NonZero<u_N>, NonZero<u_M>>narrowing (bits(u_N) > bits(u_M)).- nz_
uint_ widen Conn<NonZero<u_N>, NonZero<u_M>>widening (bits(u_N) < bits(u_M)).- uint_
int_ sat Conn<u_N, i_M>non-widening cross-sign (bits(u_N) ≥ bits(i_M)).- uint_
uint Conn<u_N, u_M>widening (bits(u_N) < bits(u_M)).- uint_
uint_ narrow Conn<u_N, u_M>narrowing (bits(u_N) > bits(u_M)).