jvoegele
Bond (Design by Contract for Elixir) - 1.3.0 released: contract inheritance & refinement
Bond 1.3.0 is out.
The big story since the 1.0 release: Bond now does contract inheritance, and as of 1.3.0, Eiffel-style contract refinement. This is Design by Contract meeting the Liskov Substitution Principle — an abstraction declares contracts, and its implementations inherit (and can deliberately refine) them.
def deps do
[{:bond, "~> 1.3"}]
end
New to Bond? It brings Design by Contract to Elixir — @pre/@post/@invariant checked at runtime, with failure messages that tell you exactly what was violated and why. The Bond 1.0.0 announcement and the getting-started guide are the best on-ramps.
Contract inheritance (1.2.0)
A behaviour or protocol is a promise about a family of implementations; a contract is the formal content of that promise. Bond now lets you state it once, on the abstraction, and enforces it across every implementation — present and future.
Behaviours — declare @pre/@post on the @callback, and implementers inherit them:
defmodule Ledger do
use Bond.Behaviour
@pre positive_amount: amount > 0
@post non_negative: result >= 0
@callback withdraw(balance :: non_neg_integer, amount :: pos_integer) :: non_neg_integer
end
defmodule BankAccount do
use Bond, behaviours: [Ledger]
@impl true
def withdraw(balance, amount) when amount <= balance, do: balance - amount
end
BankAccount.withdraw/2 now enforces Ledger’s contract — though it appears nowhere in BankAccount — and a violation is attributed back to the source behaviour.
Protocols — declare contracts on a defprotocol, enforced once at the dispatch boundary, so implementations stay completely ordinary (no Bond awareness required):
defprotocol Sized do
use Bond.Protocol
@post non_negative: result >= 0
def size(data)
end
Every call through Sized.size/1 checks the contract, whichever implementation runs — and it survives protocol consolidation.
New in 1.3.0: Eiffel-style refinement
By default an implementation inherits its contracts verbatim. 1.3.0 lets it deliberately refine them, following Eiffel’s behavioural-subtyping rules — with two distinct keywords that make the (counterintuitive) variance explicit:
@pre_weakenweakens the precondition: effective precondition =inherited or weaken(preconditions may only weaken down a hierarchy — contravariance).@post_strengthenstrengthens the postcondition: effective postcondition =inherited and strengthen(postconditions may only strengthen — covariance).
defmodule SavingsAccount do
use Bond, behaviours: [Ledger]
@impl true
@pre_weaken zero_ok: amount == 0 # also accept a no-op zero withdrawal
@post_strengthen audited: log_exists?(result)
def withdraw(balance, amount), do: ...
end
The distinct keywords are the teaching: using or to weaken a precondition is exactly the Liskov-safe direction, even though it reads backwards at first. Refinement works for protocol implementations too, via use Bond.Protocol.Impl in the defimpl. (Plain @pre/@post on an inherited operation remains a compile error — that syntax was reserved precisely so refinement could slot in with zero migration debt.)
Full rules and examples: the Contract Inheritance guide.
Also since 1.0
- 1.1.0 — a performance pass: contract checks now gate through
:persistent_term(~2.6× cheaper when enabled), plus a newBond.Configruntime API for toggling contract kinds at runtime. - 1.2.1 —
<~pattern bindings are now correctly accepted in inherited-contract reference validation, and the protocol-contracts guide was merged into a single unified Contract Inheritance guide.
Stability
Everything since 1.0 is additive — no breaking changes — so 1.3.0 is a normal minor release, and the new surface is covered by Bond’s stability guarantees. Compatibility is verified across Elixir 1.16–1.20 in CI.
Thanks & feedback
The contract-inheritance design was shaped by dogfooding it in a real application and by feedback from the earlier threads — thank you. Questions, bug reports, and ideas are very welcome here or on the issue tracker.
Links
Trending in News & Updates
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #performance
- #security










First Post!
jvoegele
For anyone curious about future directions for Bond, I’ve created some forward-looking issues in GitHub issue tracker: