A little over 18 months ago I posted the original announcement for Bond — an
early cut of a Design by Contract library for Elixir. The thread surfaced excellent feedback (thanks again to @sbuttgereit, @dimitarvp, @katafrakt, @zachallaun, @jarlah, and @Asd), much of which shaped where the library went next.
Today I’m announcing Bond 1.0.0-rc.1, the first release candidate on the road to 1.0.0.
Install
def deps do
[{:bond, "~> 1.0.0-rc.1"}]
end
What’s landed since the original announcement
The things people asked for in the original thread — and a lot more.
-
Invariants (
@invariant) for struct modules. Thesubjectbinding refers to the struct instance under check; Bond detects the struct parameter in each public function’s head and threadssubjectthrough automatically. This was the most-requested missing piece in the 2024 thread. -
A “Contracts in a Concurrent World” guide that takes the dimitarvp / katafrakt / zachallaun discussion further: how
old/1interacts with state owned by other processes, the locking pattern forAgent/GenServer/ETS-backed state, and why pure functions are still the easiest place to apply contracts. -
Conditional compilation per environment. Each contract kind (
:preconditions,:postconditions,:invariants,:checks) takestrue,false, or:purge.:purgestrips the contract code at compile time — zero runtime overhead, the contract isn’t there.true/falseare also runtime-togglable viaApplication.put_env/3. Per-module overrides are supported. -
Compatibility with other libraries that override
Kernel.@/1. Bond uses the same technique as Norm. Combining them in the same module fails to compile with a clear ambiguity error rather than silently letting one win; the FAQ documents the workaround (split modules). For the broaderdef/defpcompatibility concern @Asd raised, Bond’s compiler architecture is built on@on_definition/@before_compile/@after_compileplusdefoverridableat end-of-module — it does not redefinedefordefp. -
Multi-clause contracts clarified and documented.
@pre/@postattach to the nextdefand apply across all its clauses; the per-clause story is intentionally out of scope for 1.0 and called out as a deliberate boundary in the FAQ. -
Predicate typespecs corrected to use
as_boolean(...)instead ofboolean(), so Dialyzer no longer rejects truthy non-boolean values that Bond’s predicates accept.
What 1.0 brings on top of that
The RC adds the things you want from a 1.0:
-
A documented and frozen public API surface. Every name covered by the SemVer contract is enumerated in
guides/public-api.md— module attributes, macros, operators, predicates,Bond.TestandBond.PropertyTesthelpers, telemetry events, error structs, config keys, types. Internal namespaces (Bond.Compiler.*,Bond.Runtime.*) are explicitly carved out. -
A SemVer stability promise.
guides/stability.mdspells out what patch / minor / major actually mean for Bond, what’s explicitly excluded (compile-error message text, generated-code shape, exception message text), and the deprecation policy (minimum one minor with a warning before removal in next major). -
Published overhead numbers.
guides/overhead.mddocuments both compile-time and runtime cost from a documented reference environment (M3 Max, OTP 27.2, Elixir 1.19.5), withmix run bench/...recipes for re-running on your hardware. Headlines: a:purged contract is free; an enabled@preadds ~130 ns/call; Bond’s compile-time overhead is ~10 ms per module that uses contracts. -
:warn_skipped_invariants— a new opt-out compile warning that catches the most common silent-skip footgun (an invariant-declaring module with a public function that neither matches the struct nor returns one). Three suppression scopes are provided: per-function attribute, per-moduleuseoption, global config. -
Compatibility verified across Elixir 1.16–1.19 in CI, with parallel-compile races resolved and a Dialyzer baseline for Bond’s own code.
Why a release candidate, not 1.0.0 final?
The stability guarantees in guides/stability.md lock in at 1.0.0 final, not at RC. The RC window is explicitly for soliciting feedback from the Elixir community on the public API surface before that ink dries. If you find rough edges in the docs, surprising naming choices, missing predicates, awkward composition with other libraries, or anything that smells wrong for a 1.0, please open an issue or reply here — small adjustments to the public surface are still on the table between RC and final.
Bond intentionally stays in its lane: it doesn’t try to replace typespecs, set-theoretic types, or ExUnit. It complements them with runtime-checked, parameter-and-result-and-state assertions, written next to the code they constrain, with errors that tell you exactly what failed and why.
Bug reports and feedback welcome at Issues · jvoegele/bond · GitHub, or in reply here.






















