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