jvoegele

jvoegele

Bond (Design by Contract for Elixir) - 1.3.0 released: contract inheritance & refinement

Bond 1.3.0 is out. :tada: 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_weaken weakens the precondition: effective precondition = inherited or weaken (preconditions may only weaken down a hierarchy — contravariance).
  • @post_strengthen strengthens 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 new Bond.Config runtime 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

First Post! Switch mode

jvoegele

jvoegele

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

— All posts loaded —

Where Next?

Trending in News & Updates Top

bartblast
After building Hologram and sharing updates across various places, I’ve realized there’s a lot happening that doesn’t always make it to t...
New
kip
I’m a bit excited to announce that Localize and friends are now at release 1.0. Even though it’s a 1.0 release, it stands on 8 years of w...
New
nseaSeb
Just published search_ash 0.5.0 (with search_core 0.4.0) on Hex. What’s new: synonyms You can now declare a synonym dictionary per lang...
New
polvalente
We have released v0.13 of nx, exla and torchx, along with a recently released emlx v0.4. Nx This is where the bulk of the updates happen...
New
mudasobwa
After years of struggling I made StreamData dependency optional. Finitomata.ExUnit got testing primitives for Persistence. Full back co...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement