Repository navigation
v0.4.0 - a fast lane for money, Alpine, and a linked-in core
0.4.0 makes grouped and currency input as cheap as it should be, widens where HyperCast runs, removes the shared library from the builds that never needed one, and replaces the published benchmarks with a fresh set measured on x86-64. No door changes its verdict or its fault span on any input. The full list is in CHANGELOG.md.
Highlights
- Grouped, currency and accounting input casts in about half the time.
12,345.67,$12,345.67and($12,345.67)used to go through the full normalize-then-parse engine. A second fast lane in the core now reads them in one pass: a groupedi6441–45 ns → 14, a decimal 42–50 ns → 21–24, a real 40–42 ns → 33. Every numeric door is tested with the lane on and off and must give identical answers. - Alpine works.
linux-musl-x64andlinux-musl-arm64are built, attested and shipped in every package (asmusllinux_1_2wheels for Python). Through 0.3.0 Alpine got the glibc library, which does not load under musl. - The core links in where it can.
- Swift on Linux links a static library through SwiftPM, which adds the static Linux SDK (musl) and WebAssembly to that binding.
- Go cgo builds on Linux and macOS link the core instead of embedding and
dlopening it. The binary runs from a read-only orscratchimage. - C# Native AOT publishes link the core into the executable.
- Java in a GraalVM Native Image is fast. FFM doors were being interpreted there: 7.8–9.1 µs a door against 18–52 ns on the JVM. They are now compiled: 60–111 ns. Nothing changes on the JVM, and a consumer's
native-imagebuild needs no configuration. - Python doors that return an object cost about half.
cast_uuid557 → 262 ns,cast_timestamp369 → 252 ns,cast_datetime339 → 230 ns,cast_duration581 → 262 ns. Wheels are unchanged (still oneabi3wheel per platform), and the package is now typed. - Ruby doors on the Magnus backend cost a quarter of what they did.
bool462 → 112 ns,i32468 → 133 ns,uuid629 → 223 ns,timestamp794 → 447 ns. - The native libraries report the right version. 0.3.0's libraries say 0.2.0 (behaviour was unaffected; the number was wrong). This release is built in the corrected order.
Benchmarks, re-measured
Every table was re-measured on linux-x64 (Intel Core i9-11900H) with each binding's own harness. The previous figures came from an arm64 WSL2 machine. The doors' own costs moved little; the platform parsers they are timed against moved a lot, so several verdicts changed. Each README names the machine and runtime behind its tables.
The RFC 3339 timestamp door against each platform's own parser:
| Binding | HyperCast | Platform parser | Verdict |
|---|---|---|---|
| Swift | 51 ns | 642 ns ISO8601FormatStyle |
12.6x faster |
| Java | 50.7 ns | 615 ns Instant.parse |
12.1x faster |
| Ruby (Magnus) | 447 ns | 2.97 µs Time.iso8601 |
6.6x faster |
| C# | 52.5 ns | 128 ns DateTimeOffset.TryParse |
2.4x faster |
| PHP | 538 ns | 560 ns DateTimeImmutable |
level |
| Python | 252 ns | 146 ns fromisoformat |
1.7x slower |
| Go | 77 ns | 44 ns time.Parse |
slower |
The doors win wherever the platform's parser is culture machinery, and draw or lose wherever it is a C builtin for the one shape being timed: Guid.TryParse, UUID.fromString, int(), fromisoformat. Those losses are a few nanoseconds to a few hundred, and what they buy is a format declared per call, a fault with a byte span, and the same verdict in seven languages. On messy input the gap goes the other way: 1/7/2026 3:04 PM is 20x faster than Python's strptime, 6.9x faster than a Java DateTimeFormatter, and 3.9x faster than DateTime.TryParse.
Upgrade notes
Runtime floors. Every floor that had reached end of life is raised. A consumer on an older runtime keeps resolving 0.3.0.
| Floor | |
|---|---|
| Python | 3.11 |
| Ruby | 3.3 |
| PHP | 8.2 |
| Java | JDK 25 |
| Swift | 6.2 |
Behaviour changes.
- PHP:
ext-mbstringis no longer needed;ext-ffiis the only required extension. - PHP, Python:
NumFormat::fromLocaleconv/from_localeconvnever puts one character in both separator roles. A comma-decimal locale with no thousands separator now groups on.; PHP used to throw for it. - Ruby: caller bugs raise the same exception on every backend, and the
uuiddoor returns a US-ASCII String on every backend. - Go:
LoadError()says why the core did not load, and the doors panic withErrNativeUnavailableso arecovercanerrors.Isit.-tags hypercast_dynamickeeps the previous loading cgo backend. - Swift:
NativeLibraryErroris public andLocalizedError. - C#: set
HyperCastStaticLink=falseto keep the shared library under Native AOT. - Java: the optional GraalWasm engine moves to 25.4.4.1.1. It must be the same release as the GraalVM JDK you run on.
- Unknown architectures are no longer treated as x64. Go and PHP report an unsupported platform, Swift refuses to compile, and Java falls back to its wasm backend.
- Intel macOS (
osx-x64) still ships in every package. There is no precompiledx86_64-darwingem; Ruby there runs on the Fiddle backend.
Fixes
- C#: a
NumFormatwith no currency symbol set could throwNullReferenceException;DateOrders.From(null)now throwsArgumentNullException. - C#: a Blazor WebAssembly app could not use HyperUuid and HyperCast together (duplicate symbol at link time), failed in the browser on .NET 11, and got no native link when it reached the package through a class library.
- Ruby:
HyperCast.date(text, nil)raisedTypeErroron the Magnus backend though nil is the documented default; on Alpine, 0.3.0 loaded the glibc library andavailable?was false. - Swift: a missing resource bundle crashed the process,
Cast.isAvailableincluded. The probe is nowfalseand the doors throw. - Python:
bytesinput cost about a microsecond more thanstron every door. - Java: selecting the wasm backend without GraalWasm on the classpath now says what to add.
Verifying
Every native library and static archive in this release is built by CI and carries a build-provenance attestation:
gh attestation verify go/native/linux-x64/libhypercast.so \
--repo SkunkWerkx/HyperCast --signer-repo SkunkWerkx/.github