How Effectively Agents Write D
Paolo Invernizzi
paolo.invernizzi at gmail.com
Fri Nov 6 17:07:21 UTC 2026
On Friday, 6 November 2026 at 15:23:33 UTC, Peter Alexander wrote:
> Keen to hear any and all feedback. Also interesting in what
> other ideas people have for how to measure D for agents.
Perhaps this is disrespectful - if so, I apologize in advance -
but here is our experience; *claude-code* itself provides the
answer (based on its own memories and rules).
Hi Peter,
thanks for measuring this instead of arguing it. One data point
from a production codebase, since your set is synthetic:
DeepGlance makes eye-tracking and computer-vision products, in D
since 2016, and this year most of our new D was written by Claude
agents (mostly Opus) under review: about a thousand D files
across six repos, cross-built from a Mac to Pi Zero (armv7 and
aarch64), Jetson Orin and FreeBSD jails. Your top findings match
what we see, with some differences in where the turns go.
1. Assertion failures without values. Same tax here, and we never
turned on -checkaction=context either; our test verb runs the
runtime default. The agents' own workaround is visible in the
code: about one assert in four carries a hand-written message,
and the rest cost a writeln round when they fail. So yes, the
default should change. One heads-up if it becomes the dub test
default: with -checkaction=context the frontend lowers an assert
whose operand has side effects into a comma expression early
enough that -H and -vcg-ast print an in-contract as "in ((bool
__assertOp3 = g(x);))". We hit it on 24 druntime/Phobos modules
under LDC's unittest flags. Header generation only, but worth a
fix before the flip.
2. dub's unittest configuration. Your second papercut is zero for
us because the agents never see a build configuration: our build
tool wraps "ldc2 -i -unittest -main -run module.d" behind one
verb, test this module, and the tests live beside the code. That
one-liner belongs in any D skill for agents. It also exposes a
bias in the setup: by asking for tests in separate files you
measured the un-idiomatic layout. With unittest blocks beside the
code, testing one module compiles that module's import closure
and nothing else, and that is where D's fast-loop claim actually
lives. A second arm with the idiomatic layout would tell whether
the 1.38x in turns survives it.
3. Attributes the agent adds itself. Confirmed, and curable with
one line in CLAUDE.md: no attribute unless the module's purpose
needs it. Where they are load-bearing we want the refusals: on
the Pi fleet and in ILP32 builds, nothrow and @nogc on a few
paths keep the binary alive, and the compiler telling the agent
that Clock.currTime can throw on a 32-bit time_t is review we
would otherwise do by hand. In our shared library the counts are
roughly nothrow 740, @nogc 470, @safe 120 over some 2,200
functions; @safe is rare because the code is ImportC-heavy. We
also run -preview=rvaluerefparam where it matters, for the reason
in your table. On inferring attributes for non-template
functions: the usual objection, that the interface then drifts
with the body, hits agents harder than people, because an agent
trusts the signature and does not read the body.
4. NaN. We use it as the deliberate "no value" of a measurement
and the agents handle it once told, which is the point: your SAT
case is an untold agent. A diagnostic on a read of a
NaN-initialised float would be the structural fix; a line in the
skill is the cheap one.
5. Imports. Our policy is non-selective imports, "import
std.algorithm;", because a selective list costs the agent an
edit-build round the first time it uses a symbol not on the list.
Small, but it is the kind of default that only shows at agent
speed.
What your set does not measure, and where our agents actually
spend their turns:
- ImportC. Our bindings to CUDA, TensorRT, FFmpeg,
libdatachannel, OpenSSL and libcamera go through ImportC, and
that is the papercut mine: pragma attribute(push, nogc, nothrow)
regions, POSIX headers included inside them merging structs with
core.sys.posix, a size_t re-exported by a C module clashing with
the D one, the headers of one library version against the .so of
another. A langbench problem that binds a C library (sqlite,
zlib) would compare D's ImportC against Go's cgo on fair ground,
and both are where real projects bleed.
- The compiler as oracle. Our code leans on Design by
Introspection and on CTFE, and both put the compiler in the
reviewer's chair. A component looks at what the type it gets
provides, with static if and __traits, so one generic module
covers every combination and a missing capability is a build
error with the name of what is missing. Our SQL is checked at
compile time against a declared schema twin (a Postgres parser
running in CTFE), so an agent learns of a bad query from the
build, not from a deploy to a jail. More invariants in the
compiler means fewer runtime loops, a lever Go does not have; the
price is CTFE time, which is real. Problems whose spec can be
partly checked at compile time are where I would expect D to win
on turns, not lose.
- Wall time. On our larger binaries the fresh ldc2 build is the
slow step of a turn, not the model. Your plan to time the loop
will matter more than tokens.
One more data point on agents reading D: this autumn our agents
ported the LDC 1.43.0 frontend to TypeScript
(github.com/DeepGlance/djs), verified byte for byte against ldmd2
on the dmd test suite shipped with LDC and on every
druntime/Phobos module, plain and with -unittest. The method
lesson applies to your study: the agents were only as good as
their oracle, and D's test corpus was an excellent one. The tool
may also serve your skills idea: it is a frontend that
re-analyses only the changed modules and replays the rest, built
for the editor loop; a "check before build" verb on top of it is
a cheap experiment for cutting agent turns.
If useful, we keep a short rules file for our agents (assert vs
enforce, struct vs class, the -i one-liner, the flags that bite)
that I can share as a seed for a community D skill, and I would
run a subset of langbench on Opus with it as the extra arm.
More information about the Digitalmars-d
mailing list