Skip to content

v0.4.0 - a fast lane for money, Alpine, and a linked-in core

Choose a tag to compare

@buvinghausen buvinghausen released this 02 Oct 03:52
· 152 commits to master since this release

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.67 and ($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 grouped i64 41–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-x64 and linux-musl-arm64 are built, attested and shipped in every package (as musllinux_1_2 wheels 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 or scratch image.
    • 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-image build needs no configuration.
  • Python doors that return an object cost about half. cast_uuid 557 → 262 ns, cast_timestamp 369 → 252 ns, cast_datetime 339 → 230 ns, cast_duration 581 → 262 ns. Wheels are unchanged (still one abi3 wheel per platform), and the package is now typed.
  • Ruby doors on the Magnus backend cost a quarter of what they did. bool 462 → 112 ns, i32 468 → 133 ns, uuid 629 → 223 ns, timestamp 794 → 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-mbstring is no longer needed; ext-ffi is the only required extension.
  • PHP, Python: NumFormat::fromLocaleconv / from_localeconv never 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 uuid door returns a US-ASCII String on every backend.
  • Go: LoadError() says why the core did not load, and the doors panic with ErrNativeUnavailable so a recover can errors.Is it. -tags hypercast_dynamic keeps the previous loading cgo backend.
  • Swift: NativeLibraryError is public and LocalizedError.
  • C#: set HyperCastStaticLink=false to 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 precompiled x86_64-darwin gem; Ruby there runs on the Fiddle backend.

Fixes

  • C#: a NumFormat with no currency symbol set could throw NullReferenceException; DateOrders.From(null) now throws ArgumentNullException.
  • 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) raised TypeError on the Magnus backend though nil is the documented default; on Alpine, 0.3.0 loaded the glibc library and available? was false.
  • Swift: a missing resource bundle crashed the process, Cast.isAvailable included. The probe is now false and the doors throw.
  • Python: bytes input cost about a microsecond more than str on 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