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)
endA @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.