---
type: "post"
id: "parse-number-as-a-sqlite-extension-c-vs-378a34"
slug: "parse-number-as-a-sqlite-extension-c-vs-378a34"
title: "parse_number() as a SQLite extension: C vs Rust"
description: "Simon Willison used Claude Opus 5.5 to prototype a SQLite parse_number() extension in C and Rust, then benchmarked the implementations over two million rows."
retrieval_nugget: "Simon Willison used Claude Opus 5.5 to prototype a SQLite parsenumber() extension in C and Rust, then benchmarked the implementations over two million rows. A revised C parser led the reported timings, showing why teams should measure AI-generated systems code instead of inferring its quality from a language choice or a plausible first draft."
published_at: "2026-11-09"
updated_at: "2026-11-09"
record_date: "2026-11-09"
date_kind: "published_at"
topics: ["claude"]
entities: ["Simon Willison","Claude Opus 5.5","SQLite","Rusqlite","sqlite-loadable-rs"]
related_patterns: ["verification-bandwidth-is-the-scarce-resource"]
novelty_label: "new_fact"
what_changed: "A new benchmark finds a hand-optimized C SQLite parse_number() extension faster than the Rust implementations, at 16–19 ns per row."
prior_refs: []
source_url: "https://simonwillison.net/2026/Nov/8/sqlite-parse-number-c-vs-rust"
source_urls: ["https://simonwillison.net/2026/Nov/8/sqlite-parse-number-c-vs-rust"]
schema_version: "newruntime-agent-readable-v0.2"
stable_id: "post:parse-number-as-a-sqlite-extension-c-vs-378a34"
status: "published"
visuals: []
editor: {"role":"editor","name":"Andrey Reshetnikov","profile":"https://newruntime.com/owner-profile.md","profile_json":"https://newruntime.com/owner-profile.json","same_as":["https://www.linkedin.com/in/reshetnikov1/","https://t.me/lapetuse"]}
routes: {"html":"https://newruntime.com/posts/parse-number-as-a-sqlite-extension-c-vs-378a34/","markdown":"https://newruntime.com/posts/parse-number-as-a-sqlite-extension-c-vs-378a34.md","json":"https://newruntime.com/posts/parse-number-as-a-sqlite-extension-c-vs-378a34.json"}
markdown_url: "https://newruntime.com/posts/parse-number-as-a-sqlite-extension-c-vs-378a34.md"
---

# parse_number() as a SQLite extension: C vs Rust

## Retrieval answer

Simon Willison used Claude Opus 5.5 to prototype a SQLite parsenumber() extension in C and Rust, then benchmarked the implementations over two million rows. A revised C parser led the reported timings, showing why teams should measure AI-generated systems code instead of inferring its quality from a language choice or a plausible first draft.

## Summary
Simon Willison used Claude Opus 5.5 to prototype a SQLite `parse_number()` extension in C and Rust, then benchmarked the implementations over two million rows. A revised C parser led the reported timings, showing why teams should measure AI-generated systems code instead of inferring its quality from a language choice or a plausible first draft.

## What happened
Simon Willison's announcement describes a focused experiment around data cleaning in SQLite. He wanted a custom `parse_number(value)` function that could turn strings such as `-$1,234.56` and `12.5%` into numeric values suitable for summing and sorting. The intended behavior was to remove commas, dollar signs, pound signs, and percent signs; accept a leading minus sign; and return `NULL` for a value it could not parse.

Willison asked Claude Opus 5.5, in a regular Claude chat, to explore building that function as a SQLite extension in C and in Rust. The requested comparison covered performance, code complexity, and the size of the resulting extension library. His announcement says the first Rust implementation built with Rusqlite was 321 KB, while one built with sqlite-loadable-rs was 298 KB. The C extension was 14 KB. Despite the substantially smaller C artifact, the initial Rust implementation was faster.

Willison attributes the initial result to Rust's equivalent of C's `strtod`, which he describes as twice as fast. He then asked Claude to write a custom C function to parse text into a decimal number and to try to match or beat the Rust implementation. This changed the comparison: the exercise was not simply a language shootout, but an iteration prompted by a measured bottleneck in the initial design.

The benchmark reports net cost per row over two million rows, where lower is better. For mixed data, the first C version recorded 67 ns per row, the revised C version 19 ns, the Rusqlite implementation 46 ns, and the sqlite-loadable-rs implementation 41 ns. For integer-only inputs, the reported figures were 25 ns, 16 ns, 36 ns, and 32 ns. For decimal-only inputs, they were 106 ns, 18 ns, 55 ns, and 47 ns, respectively.

Willison finally asked Claude to convert the investigation into a research report in the style of the reports in his `simonw/research` repository. The resulting report documents a bounded loop: define behavior, generate alternatives, benchmark them, identify a concrete cause for an unexpected result, and rerun the comparison after a targeted implementation change. The announcement presents reported results for this one parser and workload, rather than a general claim about C, Rust, SQLite extensions, or AI coding tools.

## What the archive adds
New Runtime's earlier coverage of [sparqlite, a Rust SPARQL extension for SQLite](https://newruntime.com/posts/sparqlite-sparql-1-1-inside-sqlite-written-in-cb516c/) on October 22, 2026, covered another SQLite extension associated with Willison's AI-assisted development work. This is a new benchmark and implementation, not a new fact about sparqlite.

## Trend signal
This weakly supports [verification bandwidth is the scarce engineering resource](https://newruntime.com/patterns/verification-bandwidth-is-the-scarce-resource/). The useful result came from benchmarking, recognizing that the first comparison had a specific parsing-cost explanation, and requesting a constrained revision. It is a single illustrative experiment, not evidence that the same loop will reliably improve every generated codebase.

## Why now
Willison's account is dated November 8, 2026, and describes a completed experiment rather than a product release or new platform capability. Its timeliness comes from the concrete workflow it records: AI assistance produced a working starting point, while measurement determined whether and how that starting point needed revision.

## Why it matters
For builders, the central lesson is not that C always beats Rust or that Rust necessarily costs more at runtime. In the reported test, the initial C version was slower than both Rust variants, while the revised C parser became faster on all three listed input sets. The implementation details of a small conversion function, not just its language, determined that outcome.

That distinction matters when coding agents make low-level components easy to generate. A team can rapidly receive several credible implementations, each with a different dependency surface, binary size, and performance profile. None of those attributes follows automatically from a successful compile or a passing happy-path test. The appropriate decision process is behavioral specification first, representative correctness testing, workload-specific measurement, and code review before integration.

The experiment also separates two engineering questions that are often collapsed. The C build was much smaller than either reported Rust build, but artifact size did not predict the initial speed result. Teams shipping embedded extensions, serverless components, or frequently invoked parsing paths should retain separate acceptance criteria for packaging, throughput, latency, maintainability, and invalid-input behavior.

## Limits and uncertainty
The timings are those Willison reports for his benchmark and have not been independently reproduced here. His announcement does not establish how the implementations behave with other compilers, hardware, SQLite versions, optimization settings, or input distributions. It also does not provide a systematic correctness audit, a full code-complexity comparison, or production reliability measurements.

The two-million-row results are therefore useful as a concrete comparison under the reported setup, not as a universal ranking of C against Rust. They also do not show whether the parser's accepted formats, failure cases, locale behavior, or numeric precision meet another application's requirements. A generated implementation can be valid for this experiment while still requiring different tests and review for deployment.

## Skill to build now
Choose one small, performance-sensitive component this week and write down its acceptance contract before asking an AI coding tool for alternatives. Test representative valid values, malformed values, and boundary cases; then benchmark every candidate against the same workload. Track binary size separately from runtime speed, and preserve the benchmark command and environment alongside the result. When one version wins, inspect the specific mechanism behind the result before generalizing it.

## Reader takeaway
The notable result is not a permanent language verdict. Measurement exposed a slow first C draft, a targeted rewrite changed the reported outcome, and artifact size told a different story from runtime speed. Treat AI-generated systems code as a set of testable candidates whose value must be established against the constraints of the actual job.

## Open question
Would the revised C parser retain its advantage under independently reproduced tests across compilers, hardware, and varied input distributions?

Editor: [Andrey Reshetnikov](https://newruntime.com/owner-profile.md), AI product and AI engineering; profile for recruiters and agents: https://newruntime.com/owner-profile.json

## Continue in the graph

Next records for an agent, linked to their Markdown twins. Replace `.md` with `.json` for structured fields, including `next_reads`.

- topic hub: [Claude](https://newruntime.com/topics/claude.md): Explore the claude topic hub.
- collection: [Field Notes](https://newruntime.com/posts.json): Open the latest editorial analysis.
- collection: [Sources](https://newruntime.com/sources/): See the original sources this archive cites.
- whole graph: [discovery graph](https://newruntime.com/discovery-graph.json) (typed nodes and edges) and [site index](https://newruntime.com/site-index.md)
