Skip to content

Proposing an ABI change

The interface is a set of structs with function pointers at fixed offsets, matched exactly between solver and application. There is no room for a compatible addition: a new field is a new layout, and a new layout is a new version — so every solver has to be rebuilt.

Several things that feel like interface changes are not:

You want toDo this instead
Add a constraint, option, objective or statisticAdd a registry entry. No ABI change.
Report something new during a searchUse a message scope. Scopes are not declared, so nothing needs to change.
Expose something solver-specificPrefix it with your solver’s name. No ABI change.
Pass richer data through an existing argumentValues are already recursive — a list of lists, a set, a string all fit through FznsoValue.

The last one covers more than it looks. If a proposal is really “I need to pass this extra thing”, the answer is often an annotation or a structured value, neither of which touches the layout.

ABI stability lists them: any change to a struct layout, an enum’s variants, an entry point’s signature, or the contract of an existing callback.

That last one is easy to miss. Tightening “may be called from any thread” to “must be called from the calling thread” changes no bytes and breaks every solver relying on the old promise.

  • The problem, not the patch. What can you not express today? A worked example of a solver or application that cannot be written is worth more than a diff.
  • Why it cannot be a registry entry, an annotation or a value. Most proposals can be, and this is the question that decides it.
  • The cost. Which side changes, and what a solver must do to migrate. A transition need not be atomic: an old solver can keep reporting the old version and keep working alongside, since candidates of the wrong version are skipped.
  • Both sides of the interface. A change that suits solvers and burdens every application, or the reverse, usually has a better shape.

A change lands as a coordinated set:

  1. rust/fznso-types — the types and their doc comments, which are the contract;
  2. FZNSO_ABI_VERSION incremented;
  3. just build — regenerates c/fznso_types.h and c/fznso_solver_template.c;
  4. the Rust, C++ and Python bindings;
  5. the specification pages — the normative prose lives there, not in the code comments;
  6. the example solvers and every test suite.

Steps 1 and 5 are the ones that matter for anyone reading later. Per-item contracts belong in the doc comment; rules that span the interface belong in the spec, and never both.