View Source Bond.Protocol (Bond v1.7.0)

Declare @pre/@post contracts on a defprotocol's functions and have them enforced across every implementation — present or future — at the dispatch boundary.

Like Bond.Behaviour, this is Design by Contract meeting the Liskov Substitution Principle: a protocol is a promise about a family of implementations, and a contract is the formal content of that promise. Unlike behaviour inheritance, nothing is required of the implementations — a defimpl stays completely ordinary and needs no Bond awareness.

defprotocol Sized do
  use Bond.Protocol

  @post non_negative: result >= 0
  @spec size(t) :: non_neg_integer()
  def size(data)
end

defimpl Sized, for: List do
  def size(list), do: length(list)
end

A @pre/@post precedes the def it attaches to, exactly as a contract precedes the def it attaches to in use Bond. The contract expressions reference the protocol function's declared argument names (data above) and, in a @post, result (the return value).

How it works (Option B — dispatch-layer wrapping)

defprotocol generates a dispatch function — Sized.size(data) calls Sized.impl_for!(data).size(data). Bond.Protocol wraps that one dispatch function, once, in the protocol module: at @before_compile it marks the function defoverridable and redefines it to evaluate the precondition, call super/… (the original dispatch), then evaluate the postcondition. Because the wrap is on dispatch, it applies uniformly to every implementation, and it survives protocol consolidation.

Diagnostics

A violation is attributed to the protocol and names the implementation the call resolved to: the message reads postcondition (from protocol Sized, impl Sized.List) failed in Sized.size/1, with :source_protocol and :impl on the error struct and the [:bond, :assertion, :failure] telemetry metadata.

Refinement (opt-in)

An implementation can refine its inherited contract by adding use Bond.Protocol.Impl to the defimpl block. See Bond.Protocol.Impl for details. Plain defimpl blocks that do not opt in are completely unaffected.

Scope (v1)

Direct calls to a concrete implementation module (Sized.List.size/1) bypass dispatch and are therefore not checked — only calls through the protocol (Sized.size/1) are. old/1 in a protocol @pre/@post or in @pre_weaken/@post_strengthen is not supported; compile-time :purge of protocol contracts is not supported in v1. Runtime configuration (config :bond, … and Bond.Config) applies as usual.