Skip to main content

Crate connections

Crate connections 

Source
Expand description

crates.io docs MSRV CI

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:

  1. Clear semantics.

    (x as f32) as f64 != x for many x: f64. With a Conn, 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 Conn is Copy, const-constructible, heap-free, and the crate is #![forbid(unsafe_code)].

  2. Safely composable.

    The compose! macro folds a chain of pairwise Conns into one fresh Conn<Src, Dst> at compile time. A composed Conn obeys 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 namesceil (rounds up) and floor (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, or ceil on an R-kind connection results in a compiler error.
  • Position namesupper (the upper adjoint of the L-pair) and lower (the lower adjoint of the R-pair) — match the math: a generic T: ConnK bound 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 type Conn<A, B, L> or Conn<A, B, R>. Two-sided ConnK connections ship as pub structs — zero-sized marker types implementing both ConnL and ConnR. 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 any ConnL implementor via default-method dispatch): ceil, upper, plus ceil1/2, upper1/2 lifters.
  • R-side methods on Conn<_, _, R> (and on any ConnR implementor): floor, lower, plus floor1/2, lower1/2 lifters.
  • Two-sided helpers (re-exported at the crate root): interval, round/round1/round2, truncate/truncate1/truncate2, median. All bind on T: ConnK (super-trait of ConnL + ConnR over 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

FamilyModule
IEEE-754 typesfloat
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/uNNonZero<{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 (Q007I008Q127I128)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::timetime::{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 types type A: Copy; type B: Copy; and a conn_l() projection to the L-view Conn<A, B, L>. Default methods expose .ceil() and .upper().
  • ConnR — symmetric capability trait whose conn_r() projects to the R-view Conn<A, B, R>. Default methods expose .floor() and .lower().
  • ConnK — super-trait ConnL + ConnR over the same (A, B) pair; the two-sided helpers (round, truncate, …) bind on ConnK and 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 given

So 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 to a ≤ a, which is always true, so ceil(a) ≤ b for every b ∈ B. The smallest such b is b₁, so ceil(a) = b₁.
  • R-Galois inner(b) ≤ a ⟺ b ≤ floor(a). The LHS reduces to a ≤ a, always true, so b ≤ floor(a) for every b, giving floor(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
MSRVRust 1.88 (matches rust-toolchain.toml)
Edition2024
LicenseMIT (see LICENSE-MIT)

Optional cargo features:

FeatureWhat it enablesToolchain
fixedconnections::fixed::{i008,…,u128} Q-format ladders, float→Q bridges, Q.0 primitive isos, and signed normalized bit isosstable
proptestRe-exports connections::prop::arb (proptest strategies) for downstream test suitesstable
macrosRe-exports the internal codegen macro families under connections::macros / crate-root macro pathsstable
timeCivil-calendar and time-span Conns backed by the time cratestable
hifiNanosecond-precision hifitime::Duration / Epoch Connsstable
f16IEEE binary16 connections (F016, F032F016, F064F016) and their proptest strategiesnightly (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 infalliblenightly (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 --workspace

Every 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:

LawStatement
conn_galois_lceil(a) ≤ b ⟺ a ≤ inner(b)
conn_galois_rinner(b) ≤ a ⟺ b ≤ floor(a)
conn_closure_la ≤ inner(ceil(a)) (unit)
conn_closure_rinner(floor(a)) ≤ a
conn_kernel_lceil(inner(b)) ≤ b (counit)
conn_kernel_rb ≤ floor(inner(b))
conn_monotone_la₁ ≤ a₂ ⟹ ceil(a₁) ≤ ceil(a₂) ∧ floor(a₁) ≤ floor(a₂)
conn_monotone_rb₁ ≤ b₂ ⟹ inner(b₁) ≤ inner(b₂)
conn_idempotentinner ∘ 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 family

Per-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).

codetypemeaning
F016float::F016 (gated)IEEE binary16
F032F032IEEE binary32
F064F064IEEE binary64
F128(deferred — f128 unstable)IEEE binary128
I008i8signed 8-bit
I016i16signed 16-bit
I032i32signed 32-bit
I064i64signed 64-bit
I128i128signed 128-bit
U008u8unsigned 8-bit
U016u16unsigned 16-bit
U032u32unsigned 32-bit
U064u64unsigned 64-bit
U128u128unsigned 128-bit
USZEusizepointer-width unsigned (all-letter side)
ISZEisizepointer-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.0

Each 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::F064F032F064 → F032 (lossy IEEE narrowing).
  • core::f064::F064F016F064 → F016 (direct f64 → IEEE binary16, f16 feature).
  • core::f032::F032F016F032 → F016 (f32 → IEEE binary16, f16 feature).
  • core::u008::U008U016u8 → u16 saturating widen.
  • core::i008::I008I016Extended<i8> → i16 (signed widening, range-extended source).
  • core::u008::U008I016Extended<u8> → i16 (unsigned source into signed target).
  • core::i016::I016I008i16 → i8 signed-narrowing saturating cast.
  • core::u064::U064U008u64 → u8 unsigned-narrowing saturating cast.
  • core::u008::U008I008u8 → i8 non-widening cross-sign (right-Galois single-sided).
  • core::i016::I016U008i16 → u8 cross-sign narrowing (negative-clip + saturate).
  • fixed::u008::Q008Q007FixedU8<U8> → FixedU8<U7> (Q0.8 ↔ Q1.7, the 7-bit MIDI velocity format).
  • fixed::u016::Q016Q015FixedU16<U16> → FixedU16<U15> (Q0.16 ↔ Q1.15, canonical signed-PCM-equivalent unsigned audio amplitude).
  • fixed::u032::Q032Q031FixedU32<U32> → FixedU32<U31> (Q0.32 ↔ Q1.31, the canonical 32-bit normalised-amplitude format).
  • core::i008::I008N008i8 → NonZeroI8 (asymmetric adjoint at zero: floor(0) = -1, ceil(0) = +1).
  • fixed::i008::Q000I008FixedI8<U0> ↔ i8 cross-crate iso
  • fixed::i016::Q015I016 — signed normalized FixedI16<U15> ↔ i16 bit 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 of x as an Interval<A> (closed cell [lo, hi] ⊆ A sharing x’s B-projection; Interval::Empty for NaN-bearing inputs)
  • truncate, truncate1, truncate2 — round-toward-zero through the triple
  • round — round-to-nearest f32 of true π (ties broken toward zero)
  • round1 — Newton step on sin near π
  • round2 — catastrophic-cancellation recovery
  • median — 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 markers L and R, the ConnL / ConnR / ConnK capability traits for adjoint connections, and the operations on a Conn (kind-gated accessors and lifters, plus two-sided rounding helpers on ConnK).
core
Connections among Rust core/std primitive typesiN/uN std integers, IEEE floats (f16/f32/f64), bool, char, and core::num::NonZero<T>. The crate’s only top-level domain split is between this module (Conns whose endpoints all ship in core / std) and crate::fixed (Conns where at least one endpoint is a fixed-crate Q-format wrapper).
extended
Extended<T> wraps a totally-ordered T with NegInf (bottom) and PosInf (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 to NegInf/PosInf when the finite range is exceeded.
fixedfixed
Connections among fixed-crate Q-format types — the FixedI<N><Frac> / FixedU<N><Frac> wrappers and their cross-crate isomorphisms to the matching std primitive integers. The core/fixed split between crate::core and this module is the crate’s only top-level domain distinction: every Conn whose endpoints all ship in core/std lives in crate::core; every Conn where at least one endpoint is a fixed-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.
hifihifi
Galois connections among hifitime types.
interval
Order-theoretic intervals: a closed [lo, hi] interval in a preordered set, with an Empty variant covering the incomparable case.
lattice
Lattice hierarchy.
macrosmacros
Codegen macros for downstream crates building their own bounded-integer / Q-format / NonZero / iso lattices on top of this crate. Gated on the macros cargo feature.
prelude
Idiomatic batch import of the full Conn API, grouped into three analogous polarity surfaces brought into scope together.
prop
Property predicates and proptest strategies for testing consumers of this crate’s algebras.
timetime
Galois connections among time-domain types, spanning both the time crate and std::time.
uhlcuhlc
uhlc Conns

Macros§

compose
Forwarding alias for compose_l! — mirrors the K = L default of Conn. Use when composing L-side conns and you’d rather not spell the side explicitly. Error spans may point at this forwarder rather than compose_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 ConnL and ConnR impls whose conn_l / conn_r methods build the projection via compose_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 a pub const of type Conn<A, B>.
conn_r
Declaration-form macro: ship a one-sided right-Galois Conn (inner ⊣ floor) as a pub const of type Conn<A, B, R>.
ext_int
Conn<Extended<$A>, $B> widening with Extended source (left-Galois). Covers both I??I?? (signed widening) and U??I?? (unsigned-into-signed widening) — the body is identical, only the source’s MIN/MAX differ.
float_ext_int
Emit a full adjoint-triple Conn Conn<N5<$float>, Extended<$int>> (impls ConnL + ConnR) via conn_k!.
float_ext_int_l
Emit an L-only Conn Conn<N5<$float>, Extended<$int>, L>.
float_fixedfixed
Emit a full adjoint-triple Conn Conn<N5<$float>, Extended<$Fixed<$Frac>>> (impls ConnL + ConnR) via conn_k!.
float_fixed_lfixed
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 ConnK parent (an iso or other triple marker) through the Extended functor, emitting a fresh unit-struct marker that impls ConnL and ConnR on Extended<A>Extended<B>.
lift_l
Lift an L-Galois Conn (or any ConnL impl) through the Extended functor.
lift_r
Lift an R-Galois Conn (or any ConnR impl) through the Extended functor. Mirrors lift_l!: produces a fresh Conn<Extended<A>, Extended<B>, R> whose synthetic markers map identically and whose Finite arm dispatches through the parent’s lower / floor.
nz_int_ext
Conn<i<N>, NonZero<i<N>>> — saturate iN onto NonZero<iN> with asymmetric rounding at zero. Source is the bare primitive (not Extended<iN>) so there are no infinities at the source-side plateau to break the Galois law: every iN value other than 0 is itself a NonZero<iN>, and 0 sandwiches between NonZero(-1) and NonZero(+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 of nz_int_ext!. floor and ceil collapse identically at 0 because there is no NonZero ≤ 0 on the unsigned side: both pick NonZero(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)).