feat(rust): mimalloc allocator; README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 4aa94e2da6a81f0cf3a3219c5d58490c95316470 parent 9aa00e7
5 files changed, +95 −0
  • apps/rust/Cargo.lock +19 −0
  • apps/rust/Cargo.toml +1 −0
  • apps/rust/DEPS +1 −0
  • apps/rust/README.md +70 −0
  • apps/rust/src/main.rs +4 −0
diff --git a/apps/rust/Cargo.lock b/apps/rust/Cargo.lock
index edb224f..98b54f3 100644
--- a/apps/rust/Cargo.lock
+++ b/apps/rust/Cargo.lock
@@ -297,6 +297,15 @@ version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
+[[package]]
+name = "libmimalloc-sys"
+version = "0.1.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "libsqlite3-sys"
version = "0.38.2"
@@ -342,6 +351,15 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+[[package]]
+name = "mimalloc"
+version = "0.1.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
+dependencies = [
+ "libmimalloc-sys",
+]
+
[[package]]
name = "mime"
version = "0.3.17"
@@ -615,6 +633,7 @@ dependencies = [
"getrandom",
"http-body-util",
"maud",
+ "mimalloc",
"rusqlite",
"serde",
"tokio",
diff --git a/apps/rust/Cargo.toml b/apps/rust/Cargo.toml
index e553449..0ac0263 100644
--- a/apps/rust/Cargo.toml
+++ b/apps/rust/Cargo.toml
@@ -12,6 +12,7 @@ path = "src/main.rs"
axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio", "form", "query"] }
getrandom = "0.4.3"
maud = "0.27.0"
+mimalloc = "0.1.52"
rusqlite = { version = "0.40.2", features = ["bundled"] }
serde = { version = "1.0.229", features = ["derive"] }
tokio = { version = "1.54.1", features = ["rt-multi-thread", "net", "signal", "macros"] }
diff --git a/apps/rust/DEPS b/apps/rust/DEPS
index 263944e..7651027 100644
--- a/apps/rust/DEPS
+++ b/apps/rust/DEPS
@@ -1,6 +1,7 @@
axum
getrandom
maud
+mimalloc
rusqlite
serde
tokio
diff --git a/apps/rust/README.md b/apps/rust/README.md
new file mode 100644
index 0000000..322a565
--- /dev/null
+++ b/apps/rust/README.md
@@ -0,0 +1,70 @@
+# todo-rust
+
+TodoMVC for the CARLOS bakeoff, in Rust. The server renders every page
+and handles every action as a plain form POST, so the app works with
+JavaScript off. A small ES module (`static/app.js`) sends the same POSTs
+with `fetch` and swaps in the updated list, so with JavaScript on it
+behaves like the reference TodoMVC.
+
+Live at [https://todo-rust.bfj.oncarlos.com](https://todo-rust.bfj.oncarlos.com).
+
+## The stack, and why
+
+| Piece | Choice | Why |
+|---|---|---|
+| HTTP server and router | [axum](https://docs.rs/axum) 0.8 on tokio (hyper underneath) | The default choice for a Rust web service today. It serves a unix socket and a TCP port through the same `axum::serve`, and graceful shutdown on SIGTERM is one call |
+| Templates | [maud](https://maud.lambda.xyz) | HTML written as Rust macros and checked at compile time. Every interpolated value is escaped unless you opt out, which is what you want for user-typed titles |
+| SQLite | [rusqlite](https://docs.rs/rusqlite) with `bundled` | SQLite is compiled from source into the binary, so there is no shared library to find at run time. One connection behind a mutex, used from tokio's blocking pool |
+| Allocator | [mimalloc](https://docs.rs/mimalloc) | musl's own allocator is slow when many threads allocate; mimalloc is the usual fix for static musl builds |
+| Randomness | [getrandom](https://docs.rs/getrandom) | 128-bit cookie tokens straight from the OS |
+| Forms and query strings | [serde](https://serde.rs) (through axum's `Form` and `Query`) | |
+
+Flags are parsed by hand (there are three), and cookies too (there is
+one). The shared CSS files are compiled into the binary with
+`include_str!`, so they are served byte for byte.
+
+Lists are kept apart by a `todo_list` cookie holding 128 random bits.
+A cookie the app didn't issue gets a fresh list. Every query names the
+list, so an id from someone else's list is a 404. A POST whose `Origin`
+isn't the app's own is a 403. See `AGENTS.md` for the tenancy rules.
+
+### Layout
+
+- `src/main.rs`: flags, socket or TCP listener, SIGTERM.
+- `src/app.rs`: routes and handlers, including `GET /`.
+- `src/views.rs`: the markup, following `shared/reference.html`.
+- `src/db.rs`: schema, migrations (additive, tracked by `PRAGMA user_version`) and queries.
+- `static/app.js`: the JavaScript layer.
+- `tests/http.rs`: tests that drive the router in-process.
+
+### Without JavaScript
+
+The reference markup has checkboxes and a double-click to edit, neither
+of which can post a form. The page adds a submit button over each
+checkbox (and over the toggle-all arrow) and a link over each title that
+opens `?edit=<id>`. They're transparent and sit exactly on the parts of
+the design they stand in for, so the page looks the same either way. A
+`<noscript>` stylesheet turns them on; with JavaScript they stay
+`display: none`.
+
+## Build, run, test, deploy
+
+Toolchains come from the repo: the Makefile sources `scripts/env.sh`
+itself. Build output goes in `.cache/target` and `dist/`.
+
+```sh
+make build # dist/app for this machine
+make build-arm64 # dist/app-linux-arm64, static (cargo zigbuild, musl)
+make test # cargo test
+dist/app --listen 127.0.0.1:8080 --db "$TMPDIR/rust.db"
+../../scripts/test-app.sh http://127.0.0.1:8080 "$TMPDIR/rust-suite.json"
+../../scripts/boot-check.sh dist/app-linux-arm64
+make deploy MESSAGE="what changed"
+```
+
+`make clean` removes `dist/` and this crate's own compiled files.
+`make clean-deps` removes all of `.cache/target`, so the next build
+recompiles every dependency (the downloaded sources in `~/.cargo` stay).
+
+The version `/api/version` reports is the git short SHA at build time,
+stamped in through the `APP_VERSION` environment variable.
diff --git a/apps/rust/src/main.rs b/apps/rust/src/main.rs
index 19ae709..3cb0343 100644
--- a/apps/rust/src/main.rs
+++ b/apps/rust/src/main.rs
@@ -11,6 +11,10 @@ use std::process::ExitCode;
use tokio::signal::unix::{SignalKind, signal};
+// musl's allocator is slow under many threads; mimalloc is a drop-in fix.
+#[global_allocator]
+static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
+
/// The git short SHA, stamped in by `make` (see the Makefile).
const VERSION: &str = match option_env!("APP_VERSION") {
Some(v) => v,