jvoegele
Bond 1.0.0 is out — the first stable release. ![]()
After three release candidates and a good deal of dogfooding, Bond has reached 1.0.0. The SemVer stability guarantees described in the docs are now in force: the public API surface is frozen, and changes to it follow a real deprecation policy from here on.
def deps do
[{:bond, "~> 1.0"}]
end
What Bond is
Bond brings Design by Contract to Elixir. You write a function’s obligations and guarantees right next to the code they constrain, and Bond checks them at runtime with failure messages that tell you exactly what was violated and why:
defmodule Account do
use Bond
@pre sufficient_funds: amount <= account.balance, positive: amount > 0
@post drawn_down: result.balance == account.balance - amount
def withdraw(account, amount) do
%{account | balance: account.balance - amount}
end
end
Beyond @pre/@post, Bond gives you @invariant for struct modules (a property that must hold of every instance), check/1,2 for inline assertions, and old/1 for referring to a value as it was on entry. Contracts are conditionally compiled per environment — each kind (:preconditions, :postconditions, :invariants, :checks) can be true, false, or :purge, and :purge strips the contract code entirely so there is zero runtime cost in production.
Bond stays in its lane: it doesn’t replace typespecs, set-theoretic types, or ExUnit. It complements them with runtime-checked assertions over parameters, results, and state.
Because @invariants are machine-checkable, Bond can also drive them: Bond.PropertyTest.invariants_hold/2 runs randomized sequences of constructor/transformer/observer operations (via StreamData) and uses your invariants as the oracle across every reachable state — property-based testing for free, once you’ve stated the invariant. (Details in the docs.)
The getting-started guide is the best on-ramp.
What changed across the release-candidate cycle
The rc.1 announcement covered the bulk of the feature set — invariants, the concurrency guide, conditional compilation, library coexistence, corrected predicate typespecs. A few things landed after it that weren’t yet announced here:
- rc.2 — Bond can share a single module with other libraries that override
Kernel.@/1or wrap your functions (Norm, anything built ondecorator):use Bond, at_annotations: falseplus tolerance of externally-generated override clauses. Contract labelling was also unified on the single keyword-list form (@pre positive: x > 0). - rc.3 — the property-testing entry point was split into two clearly-named macros:
contract_holds/2for a single function and the newinvariants_hold/2for the stateful module-sequence form above. - rc.4 — a soundness fix for
@invarianton heterogeneous multi-clause functions: a struct clause’s pre-invariant could be silently skipped when a sibling clause matched a non-struct value. Now fixed.
The full history is in the changelog.
What 1.0.0 means for you
The point of 1.0.0 is that you can now depend on Bond’s public surface with confidence:
- Every name covered by the SemVer contract is enumerated in the public API surface guide — macros, attributes, predicates, the test helpers, telemetry, error structs, config keys, types. Internal namespaces are explicitly carved out.
- The stability guarantees guide spells out what patch / minor / major mean in practice, what’s explicitly excluded (compile-error text, generated-code shape, exception message text), and the deprecation policy.
- Compatibility is verified across Elixir 1.16–1.19 in CI.
What’s next
With 1.0 stable, the next thread of work is contract inheritance: letting a behaviour declare @pre/@post on its callbacks so that implementing modules inherit those contracts automatically (with protocols to follow). This is design-in-progress, not a promise of timing — if the idea interests you, early discussion is very welcome on the issue tracker.
Thanks
Bond is meaningfully better for the feedback from the original 2024 thread and the RC threads — thank you to everyone who kicked the tires, filed issues, and pushed back on the design. That input is exactly what the RC window was for, and it shaped what shipped today.
Feedback, bug reports, and ideas remain welcome here or at Issues · jvoegele/bond · GitHub.
Links
Original library announcement: Bond - Design by Contract for Elixir
RC1 announcement: Bond (design by contract for Elixir) - 1.0.0-rc.1 released
Trending in News & Updates
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
Petr
This looks really great
Thank you for creating it.
Question: how could the predicates be abstracted? Meaning, If I have a predicate that applies to a certain function argument that is accepted across 3 different functions in a module, I’d like to declare the predicate only once for the argument and reuse it.
jvoegele
Hi @Petr, thank you for the kind words!
The idiomatic way to do this is to define the predicate once as an ordinary function in your module and then call it from the
@pre(or@post) of each function that needs it. Contract expressions are just plain Elixir, so any function in scope — including your own — can be called inside them:The validation logic lives in one place; each function just references it. The label (
valid_recipient:) is what shows up in the error message and generated docs, so you can keep it consistent or vary it per call site, or even omit it entirely. The predicate can be a privatedefpif you’d rather not export it — it still resolves inside contracts since the generated checks run in the same module.If the argument in question happens to be a struct defined in that same module, there’s an even more automatic option:
@invariant. Invariants encode predicates on the struct itself and are checked on entry to and exit from every public function in the module that takes or returns the struct — so you don’t repeat them per-function at all. The trade-off is that they apply module-wide (to every public function touching the struct), not to a hand-picked subset, and they’re struct-only. For the general “reuse across a few specific functions” case, the predicate-function pattern above is the way to go.There’s a worked
@invariantexample —BoundedStack, withnon_negative_capacityandsize_within_capacityinvariants — in the@invariantsection of the docs if you’d like to see it in action.Petr
Ah right, thanks, this is obvious when I see it now
I’ve missed it and I was attempting to do it on a different level.
Let’ me describe how I was thinking about it, it might still be useful to explore. I wanted to abstract in a such a way that:
def valied_email?(address), do: [valid_recepient: is_binary(address) and String.contains?(address, "@")](just to demonstrate, this does not work, the body of the predicate function is evaluated not processed by bond macros)valied_email?but rather it’s source code.valid_emailis actually a great example where giving it a name is better than printing out the full real email format validation.Something in the direction of
or better with a macro to avoid
var!but the assertion in error message
does not print it anyway. I’ve assumed it takes the AST and prints it, but I guess it takes the source as String?
jvoegele
Ah, now I see what you were reaching for: abstracting the label along with the assertion, and choosing whether the error/docs show a name or the full expanded contract. Good news: Bond can do both. Let me first answer your AST-vs-string question, since it explains everything else.
When you write
@pre <expr>, Bond captures the surface AST of<expr>and renders it for errors and docs withMacro.to_string/1— and it does not macro-expand it first. So@pre unquote(@email_predicate)prints literally asunquote(@email_predicate)because that is the surface AST (unquoteoutside aquoteis just an inert node at that layer). That’s why your attribute approach didn’t expand the way you hoped.The way to get the expansion to happen is to let a macro produce the assertion, because the expression Bond captures is then whatever the macro expands to. There are two flavours, and they map exactly onto your two goals:
1. Name it (hide a gnarly predicate behind a label)
Put a macro (or plain function) call directly in
@pre:This enforces correctly — the macro expands at the point Bond splices the check into the generated function — but the error shows the call, not its body:
Perfect for your real-email-validation case, where the name reads better than the regex.
2. Inline the full contract and abstract the label
Write a macro that emits the whole labelled
@pre:Now the label is abstracted and the error/docs show the fully expanded expression:
You can emit several
@pre/@postlines from one macro too, which covers your “abstract over a group of labelled predicates” idea.The one caveat
The predicate-defining module must itself
use Bond. This is macro hygiene: the@insideContracts’squoteresolves inContracts’s context, so if that module doesn’t use Bond, its@preisKernel.@— an attribute assignment that eagerly evaluates the right-hand side, which is theundefined variable "email"error your experiments would have hit. Withuse Bondin that module,@preis Bond’s override and everything resolves. (And you don’t needvar!—Macro.var(name, nil)unifies fine with the parameter.)Thanks for pushing on this. It’s a genuinely nice idiom, and you’ve convinced me it deserves a spot in the docs. I’ll add a FAQ entry.
nicflower
this is brilliant, great job!
I have one question, I see that you setup a way to toggle the functionality in prod. Do you think the checks should be turned off in prod? What use case do you have in mind?
jvoegele
Thank you, @nicflower, I appreciate the kind words.
Bertrand Meyer has written a lot about whether contracts should be enabled in production environments, especially in his book Object-Oriented Software Construction. Meyer’s recommendation is to leave contracts enabled; only disable them when you’ve measured a performance impact you can’t accept. The Bond docs include an Overhead guide that gives some indication of how much overhead contract evaluation adds at runtime.
Bond offers pretty flexible configuration options so you can tune precisely to your environment. The
:purgeoption completely eliminates contracts at compile-time so there is zero runtime overhead. You can also use the:overridesconfiguration option to tune specific modules, such as hotspots where performance is critical.The main use case for disabling or purging contracts is performance-sensitive code: high-throughput data pipelines, financial systems where individual function calls are on the critical path, or any hot loop where you’ve profiled and confirmed that contract evaluation is a meaningful contributor to latency. In those cases,
:overrideslets you surgically purge just the affected modules while leaving contracts active everywhere else.Note that it is important to write your code and your contracts such that there is no behavior change whether contracts are enabled, disabled, or purged. For example, don’t use preconditions for user input validation — if preconditions are disabled, then the user input validation would never occur and bad user input could potentially corrupt your system.
That said, my default is to leave all contracts enabled in production and only disable or purge when performance demands it.