Skip to content

Make WorkTable's own source std-free - #101

Closed
pathscale wants to merge 3 commits into
masterfrom
feat/no-std-core
Closed

Make WorkTable's own source std-free#101
pathscale wants to merge 3 commits into
masterfrom
feat/no-std-core

Conversation

@pathscale

Copy link
Copy Markdown
Owner

cargo check --no-default-features now passes with no errors and no warnings. The default build is unchanged and the same 926 tests pass.

What the numbers were

Counting std:: paths in the source predicts nothing useful, so the work was driven by the compiler instead. Flipping #![no_std] on produced 550 errors. They fell into three groups of very different character:

count what it was
core and alloc already have it ~170 paths fmt, mem, hash, marker, cmp, ops, iter, num, ptr, hint, any, array, future, pin, task, cell, error, atomics, Arc, Rc, Duration, VecDeque, the BTrees
the alloc prelude 528 errors Vec 356, String 68, vec! 47, format! 41, Box 26
genuinely wants an OS 22 errors every one in the persistence half

Where the line actually falls

Not where the module tree suggested. persistence looked like the std half wholesale, but the table's own mutation path builds Operation values inline, so gating the module took the insert path with it. Those types are plain data: operation and operation::util name no std path, no filesystem and no runtime, and they stay in the core.

The gate falls on what wants an operating system: engine, error (which is a path and a reason), space, task, readonly_engine, operation::batch, and the PersistenceEngine trait that speaks in batches. vacuum goes with them, needing Instant for its settle deadline and PersistenceResult for its trait; a table with no background runtime has nothing to run a defragmenter on.

Also gated: validate_persisted_state (audits a loaded file, so takes a Path), the WorkTableError::Persistence variant, the MemStat impls for SystemTime and Instant, and migration.

Two changes that are not mechanical

Hash maps move to hashbrown, which is what std's own HashMap is built on. Its get is bounded on Equivalent<K> where std's is bounded on Borrow<Q>, so a key written &1.into() no longer infers and now names its type. BatchData is a public alias, so a caller building one with std::collections::HashMap must say worktable::prelude::HashMap; the prelude re-exports it.

Generated code cannot name alloc or hashbrown, since a user crate has neither in scope, so it goes through the prelude, and the crate gains an extern crate self as worktable alias so those paths also resolve where they are emitted.

What this does not do

It does not make the crate build for a bare-metal target. arc-swap has no std feature at all, our parking_lot fork deliberately drops the generic thread parker, and data_bucket is its own piece of work. Each was measured separately by building it as its own #![no_std] crate for aarch64-unknown-none.

What it establishes is that none of the remaining distance is WorkTable's own code.

🤖 Generated with Claude Code

meh and others added 3 commits September 9, 2026 02:12
Around 170 of WorkTable's std:: paths name something core or alloc
provides outright: fmt, mem, hash, marker, cmp, ops, iter, num, ptr,
hint, any, array, future, pin, task, cell, error, atomics, Arc, Rc,
Duration, VecDeque and the BTrees. Those are a rename, and doing them
first leaves only the paths that genuinely want an operating system,
which is the list worth arguing about.

Hash maps move to hashbrown, which is what std's own HashMap is built
on. Two consequences are real rather than mechanical. hashbrown's `get`
is bounded on Equivalent<K> where std's is bounded on Borrow<Q>, so a
key written as `&1.into()` no longer infers and now names its type. And
`BatchData` is a public type alias, so a caller building one with
std::collections::HashMap has to say worktable::prelude::HashMap
instead; the prelude re-exports it.

Generated code cannot name alloc or hashbrown, since a user crate has
neither in scope, so it goes through the prelude, and WorkTable gains a
`extern crate self as worktable` alias so those paths also resolve in
the crate that emits them.

The stripe hasher becomes rustc_hash's, already a dependency, because
hashbrown does not carry DefaultHasher.

926 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`#![cfg_attr(not(feature = "std"), no_std)]` behind a default-on `std`
feature, so the crate builds exactly as before unless a caller asks for
otherwise, and `cargo check --no-default-features` now measures whether
WorkTable's own source needs an operating system.

Without std, Vec, String, Box, ToString, ToOwned, vec! and format! leave
the prelude, which was 528 of the 550 errors that flip produced. Each
module now names what it uses from alloc; the two macros come from a
`#[macro_use] extern crate alloc`. Nothing else changed, which is why
the same 926 tests pass unmoved.

Four files take Box without writing it: `#[async_trait]` and
`worktable!` both expand to `Box<dyn Future>`.

Spinning code that gave up its timeslice with `std::thread::yield_now`
goes through `util::yield_now`, which is that call under std and the
spin hint without one, there being no scheduler to yield to.

22 errors remain, every one of them in the persistence half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WorkTable's own source now compiles with `--no-default-features`, with
no errors and no warnings. The default build is unchanged and the same
926 tests pass.

The line is not where the module tree suggested. `persistence` looked
like the std half wholesale, but the table's own mutation path builds
`Operation` values inline, so gating the module took the insert path
with it. Those types are plain data: `operation` and `operation::util`
name no std path, no filesystem and no runtime, so they stay in the
core, and the gate falls on what actually wants an operating system:
`engine`, `error` (which is a path and a reason), `space`, `task`,
`readonly_engine`, `operation::batch`, and the `PersistenceEngine`
trait that speaks in batches.

`vacuum` goes with them. It needs `Instant` for its settle deadline and
`PersistenceResult` for its trait, and a table with no background
runtime has nothing to run a defragmenter on. The support surface it
alone calls follows it: the live-cell counters, the vacuum row move,
and the ART topology export.

Also gated: `WorkTable::validate_persisted_state`, which audits a
loaded file and so takes a `Path`; the `WorkTableError::Persistence`
variant; the `MemStat` impls for `SystemTime` and `Instant`; and
`migration`, which opens files.

This does not make the crate build for a bare-metal target. Its
dependencies still want std, and arc-swap, parking_lot and data_bucket
are each their own piece of work. What it does establish is that none
of the remaining distance is WorkTable's own code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pathscale

Copy link
Copy Markdown
Owner Author

Superseded by #102, which contains every commit on this branch: git merge-base --is-ancestor origin/feat/no-std-core origin/feat/tunable-page-stride passes. Reviewing them separately means reviewing the same diff twice, and #102 is where the measurements for both live. Closing so the repo has one PR for this work rather than a stack.

@pathscale pathscale closed this Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant