{"slug": "design-by-contract-and-effects-for-llms", "title": "Design by Contract and Effects for LLMs", "summary": "Gavin Ray argues that Design-by-Contract and effects are essential for LLM-generated code, as they enable compiler-generated reports of semantic changes and ensure verifiable behavior. Ray, who notes that most code he shipped in the last year was prompted, emphasizes that these features provide 'code that verifiably does what it says on the tin.' He illustrates with examples such as a User struct with invariants and a MinHeap with ordering conditions, and suggests that effects can denote explicit capabilities like network IO.", "body_md": "- Published on\n\n# Design by Contract and effects are essential for LLM-generated code\n\n- Authors\n- Name\n- Gavin Ray\n[@GavinRayDev](https://twitter.com/GavinRayDev)\n\nIn this post, I want to discuss two language features that I think have become substantially more important as software development shifts from human-authored to LLM-authored code.\n\nLike many others, the majority of the code I have \"written\" (prompted) and shipped in the last year was not authored by me. You feed specifications to an LLM, it spits out an implementation, and you ask it to write tests to verify the behavior. You run the tests, manually poke the app to see if it behaves how you expect, and if you have the time you review the code.\n\nThis sort of development loop gives immense value to \"code that verifiably does what it says on the tin.\"\n\nThere are two programming language features that facilitate this, and I'm convinced that their value is tenfold in this new era of machine-generated code:\n\n- Design-by-Contract\n- Effects\n\nThe net result of these is the possibility to have compiler-generated reports of *semantic* changes in a PR, like:\n\n```\nEffects added: PaymentProcessor.process\n  + net.connect\n  + retry.nondeterministic\n\nPostcondition weakened: Ledger.append\n  - ensures ledger.length == old(ledger.length) + 1\n```\n\nAI (Dis)use Disclaimer:No part of the prose was machine-generated. You will not find machine-written prose on this blog. I consider it deeply disrespectful.\n\nDesign-by-Contract\n\nDesign-by-Contract (DbC) is language/syntax feature that allows writing preconditions, postconditions, and invariants (always-true) as part of the signature of methods and structs/classes.\n\nIt's somewhere between a mix of ad-hoc testing and formal specification. In many languages, contracts have build-time \"level\" switches which toggle the behavior and checks from compile-time to run-time, for performance reasons.\n\nA picture is worth a thousand words, so rather than continue describing Contracts, let me give some (hopefully) self-explanatory examples.\n\nSuppose we have a `User`\n\ntype:\n\n```\nstruct User {\n    email: String\n    email_verified: Bool\n}\n```\n\nNothing prevents this state:\n\n```\nUser {\n    email: \"\",\n    email_verified: true\n}\n```\n\nBut with Design-by-Contract, we can encode a few simple invariants to rule it out:\n\n```\nstruct User {\n    email: String\n    email_verified: Bool\n\n    invariant email.is_valid_email()\n    invariant email_verified implies !email.is_empty()\n}\n```\n\nChanging the email can then specify what else must change:\n\n```\nfn change_email(user: &mut User, new_email: String)\n    requires new_email.is_valid_email()\n\n    ensures user.email == new_email\n    ensures user.email_verified == false\n```\n\nWithout the postcondition `ensures`\n\n, a generated implementation might update the address while leaving `email_verified`\n\nset to `true`\n\n.\n\nData structures whose properties can be encoded as invariant conditions are particularly well suited to this sort of design:\n\n```\nstruct MinHeap<T: Ordered> {\n    items: Array<T>\n\n    invariant forall i in 1..<items.length:\n        items[parent(i)] <= items[i]\n}\njs\nstruct BTreeNode<K, V, const ORDER: usize> {\n    keys: Array<K>\n    values: Array<V>\n    children: Array<NodeRef>\n\n    is_leaf: bool\n\n    invariant keys.length == values.length\n    invariant keys.length <= ORDER - 1\n\n    invariant strictly_increasing(keys)\n\n    invariant is_leaf\n        implies children.length == 0\n\n    invariant !is_leaf\n        implies children.length == keys.length + 1\n}\n```\n\nEffects\n\nEffects are a way to denote explicit capabilities in methods.\n\nTypically these are behaviors such as \"file/network IO, memory allocation, state changes\", etc. By requiring that method effects be explicit, you can use a method's signature as a verified contract of its permitted behaviors.\n\nAs one example, suppose we have a method:\n\n``` php\nfn load_settings(path: Path) -> Settings\n```\n\nThe return type says nothing about where the settings come from or what the operation may do.\n\nAn effect-aware signature does:\n\n``` php\nfn load_settings(path: Path) -> Settings\n    requires path.exists()\n    requires path.is_file()\n\n    ensures result.is_valid()\n\n    effects {\n        fs.read(path),\n        alloc\n    }\n```\n\nNow compare that with a function that merely parses settings already held in memory:\n\n``` php\nfn parse_settings(contents: String) -> Settings\n    requires !contents.is_empty()\n\n    ensures result.is_valid()\n\n    effects {\n        alloc\n    }\n```\n\nThese functions may return the same TYPE, but they have meaningfully different OPERATIONAL behavior. That difference should be visible at the call site.\n\nA few more examples:\n\n- Cache\n`get`\n\nthat updates`recency`\n\ninternal metadata\n\nAt first glance, you might think that the below method is read-only:\n\n``` php\nfn get(cache: &mut Cache<K, V>, key: K) -> Option<V>\n```\n\nBut many caches maintain usage statistics, and it may be the case that our `get`\n\nmethod is mutating. The below signature is explicit about behavior like this:\n\n``` php\nfn get(cache: &mut Cache<K, V>, key: K) -> Option<V>\n    ensures result.is_some() == old(cache.contains(key))\n    ensures cache.entries == old(cache.entries)\n\n    effects {\n        state.write(cache.recency)\n    }\n```\n\nThe cache contents do not change, but its eviction metadata does.\n\n- A function that must not allocate\n\nSuppose that we have a method which writes/encodes some value into a caller-supplied buffer:\n\n``` php\nfn encode(\n    value: Message,\n    destination: &mut ByteBuffer\n) -> usize\n    requires destination.remaining >= value.encoded_size()\n\n    ensures result == value.encoded_size()\n    ensures destination.position == old(destination.position) + result\n\n    effects {\n        memory.write(destination)\n    }\n```\n\nThe implementation can write into the supplied buffer but cannot create a temporary array or string (because it does not possess the `alloc`\n\neffect)", "url": "https://wpnews.pro/news/design-by-contract-and-effects-for-llms", "canonical_source": "https://gavinray97.github.io/blog/design-by-contract-and-effects-for-llms", "published_at": "2026-08-04 19:04:40+00:00", "updated_at": "2026-08-04 19:22:55.216330+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "developer-tools"], "entities": ["Gavin Ray"], "alternates": {"html": "https://wpnews.pro/news/design-by-contract-and-effects-for-llms", "markdown": "https://wpnews.pro/news/design-by-contract-and-effects-for-llms.md", "text": "https://wpnews.pro/news/design-by-contract-and-effects-for-llms.txt", "jsonld": "https://wpnews.pro/news/design-by-contract-and-effects-for-llms.jsonld"}}