View Source Bond.Protocol (Bond v1.2.1)

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.

Scope (v1)

Inherited contracts are immutable: implementations enforce the protocol's contracts verbatim and cannot refine them (that is the refinement feature's job). 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 @post and compile-time :purge of protocol contracts are not supported in v1; runtime configuration (config :bond, … and Bond.Config) applies as usual.