Skip to content

v0.8.0 - a char door, one generic door, the platform's own format, and Android

Choose a tag to compare

@buvinghausen buvinghausen released this 09 Oct 04:16
· 82 commits to master since this release

0.8.0 comes from Norse Architecture, which is replacing its last hand-written scalar parsers with HyperCast and filed the two issues behind the first two highlights. It also adds one new platform family: C#, Go and Swift now reach Android. Everything is additive, and no input gets a different verdict than it did in 0.7.0. The full list is in CHANGELOG.md.

Highlights

  • A char door, in the core and every binding (#22). It reads exactly one character verbatim, checked before trimming, so " " is a space and "6" the digit six. Otherwise it reads one declared code point: decimal 65, U+0041, 0x41, &H41, or an HTML entity A / A. A surrogate or anything past U+10FFFF is OutOfRange. It is the twenty-sixth C ABI export, cast_char, and corpus/char.json is replayed by all eight suites.

    Language Door Returns
    Rust cast_char char
    C# Cast.Char char
    Java Cast.character Character
    Go Char rune
    Swift Cast.char Unicode.Scalar
    PHP, Ruby, Python Cast::char, HyperCast.char, cast_char a one-character string

    C# and Java return a scalar past U+FFFF as OutOfRange, since their char is one UTF-16 unit.

  • One door generic over every target (#21), in C#, Go and Swift. It picks the door from the requested type, so a caller holding a generic T routes through HyperCast in one call.

    • C#: Cast.Scalar<T> over bool, the numerics, Guid, DateOnly, TimeOnly, DateTimeOffset, TimeSpan, char and DateTime. Scalar<DateTime> reads RFC 3339 as UtcDateTime.
    • Go: Scalar[V ScalarTarget, T Text]. A type outside the union is a compile error.
    • Swift: Cast.scalar<T: ScalarCastTarget>(_:format:).
  • The numeric doors take the platform's own format object. A caller holding a culture or locale passes it straight through instead of building a NumFormat first. Each maps to the core's format with every lenience on, and a NumFormat is still how to declare anything stricter.

    • C#: an IFormatProvider? overload of every numeric door, Numeric<T> and Scalar<T>. null is the current culture, as in TryParse.
    • Java: a Locale overload of every numeric door. NumFormat.from(Locale) is now cached per locale (52.6 ns against 437.6 uncached).
    • Swift: a locale: overload of every numeric door, numeric and scalar.
    • PHP: every numeric door accepts NumFormat|\NumberFormatter, plus NumFormat::fromNumberFormatter. intl stays optional.
    • Ruby: Rails' separator:, delimiter: and unit: keywords on every numeric door, plus NumFormat.from_i18n for an I18n.t("number.format") Hash.
    • Python and Go keep taking a NumFormat, since neither standard library has a per-object locale to take.

    Every bridge now documents what a culture carries that the core's format cannot yet: a minus sign other than ASCII -, currency separators that differ from the number ones, and a percent symbol other than %. The roadmap plans the round that closes those gaps.

  • Android, in C#, Go and Swift, on arm64 and x86_64.

    Language How
    C# The package carries android-arm64 and android-x64, so a .NET MAUI app has the core on every platform MAUI targets with nothing but the package reference. CoreCLR loads libhypercast.so from the APK, and a Native AOT publish links it in from a static archive. The libraries are NDK-built for API 21 and 16 KB-aligned, as Android 15 devices and Google Play require.
    Go GOOS=android links go/staticlib/android_{arm64,amd64}, with the NDK's clang as CC.
    Swift Links the artifact bundle's Android variants with the Swift SDK for Android (Swift 6.3+, API 28+).

    CI builds the C# app both ways for both RIDs from a package packed in the same run, cross-builds the Go and Swift suites, and runs everything x86_64 in an emulator with 16 KB pages.

Fixed

  • Ruby: the native extension survives a compacting garbage collection. It kept a handful of Ruby objects (Success, Fault, Date, DateTime, Decimal and the two built-in formats) in a Rust static that a compacting GC could move. A door called after a compaction could then segfault. Each is now pinned when the extension loads, and a new spec compacts before calling a door.
  • Go: a build with no cgo (CGO_ENABLED=0) once again fails with only the one explanatory error, instead of also reporting undefined: symChar.

Upgrade notes

Source-compatible for every consumer except one narrow case.

  • Java: a call that passed a literal null as the format, such as Cast.i32(text, null), no longer compiles, because the new Locale overload makes it ambiguous. It already threw at run time, so no working code depends on it.
  • Rust: version = "0.8" in Cargo.toml. Under 0.x caret rules, "0.7" never resolves to 0.8.

Verifying

Every native library and static archive in this release is built by CI and carries a build-provenance attestation, the new Android libraries and archives included:

gh attestation verify go/staticlib/android_arm64/libhypercast.a \
  --repo SkunkWerkx/HyperCast --signer-repo SkunkWerkx/.github

The gems and Python wheels are attested by this repo's own release.yml, so --repo SkunkWerkx/HyperCast alone verifies them.

The CHANGELOG.md and roadmap links point at the v0.8.0 tag, so they'll only resolve once you've pushed it. If staging ends up on a different date, change the 2026-10-08 in the CHANGELOG heading to match.

C# and Java return a scalar past U+FFFF as OutOfRange, since their char is one UTF-16 unit.

  • One door generic over every target (#21), in C#, Go and Swift. It picks the door from the requested type, so a caller holding a generic T routes through HyperCast in one call.

    • C#: Cast.Scalar<T> over bool, the numerics, Guid, DateOnly, TimeOnly, DateTimeOffset, TimeSpan, char and DateTime. Scalar<DateTime> reads RFC 3339 as UtcDateTime.
    • Go: Scalar[V ScalarTarget, T Text]. A type outside the union is a compile error.
    • Swift: Cast.scalar<T: ScalarCastTarget>(_:format:).
  • The numeric doors take the platform's own format object. A caller holding a culture or locale passes it straight through instead of building a NumFormat first. Each maps to the core's format with every lenience on, and a NumFormat is still how to declare anything stricter.

    • C#: an IFormatProvider? overload of every numeric door, Numeric<T> and Scalar<T>. null is the current culture, as in TryParse.
    • Java: a Locale overload of every numeric door. NumFormat.from(Locale) is now cached per locale (52.6 ns against 437.6 uncached).
    • Swift: a locale: overload of every numeric door, numeric and scalar.
    • PHP: every numeric door accepts NumFormat|\NumberFormatter, plus NumFormat::fromNumberFormatter. intl stays optional.
    • Ruby: Rails' separator:, delimiter: and unit: keywords on every numeric door, plus NumFormat.from_i18n for an I18n.t("number.format") Hash.
    • Python and Go keep taking a NumFormat, since neither standard library has a per-object locale to take.

    Every bridge now documents what a culture carries that the core's format cannot yet: a minus sign other than ASCII -, currency separators that differ from the number ones, and a percent symbol other than %. The roadmap plans the round that closes those gaps.

  • Android, in C#, Go and Swift, on arm64 and x86_64.

    Language How
    C# The package carries android-arm64 and android-x64, so a .NET MAUI app has the core on every platform MAUI targets with nothing but the package reference. CoreCLR loads libhypercast.so from the APK, and a Native AOT publish links it in from a static archive. The libraries are NDK-built for API 21 and 16 KB-aligned, as Android 15 devices and Google Play require.
    Go GOOS=android links go/staticlib/android_{arm64,amd64}, with the NDK's clang as CC.
    Swift Links the artifact bundle's Android variants with the Swift SDK for Android (Swift 6.3+, API 28+).

    CI builds the C# app both ways for both RIDs from a package packed in the same run, cross-builds the Go and Swift suites, and runs everything x86_64 in an emulator with 16 KB pages.

Fixed

  • Ruby: the native extension survives a compacting garbage collection. It kept a handful of Ruby objects (Success, Fault, Date, DateTime, Decimal and the two built-in formats) in a Rust static that a compacting GC could move. A door called after a compaction could then segfault. Each is now pinned when the extension loads, and a new spec compacts before calling a door.
  • Go: a build with no cgo (CGO_ENABLED=0) once again fails with only the one explanatory error, instead of also reporting undefined: symChar.

Upgrade notes

Source-compatible for every consumer except one narrow case.

  • Java: a call that passed a literal null as the format, such as Cast.i32(text, null), no longer compiles, because the new Locale overload makes it ambiguous. It already threw at run time, so no working code depends on it.

Fixed

  • Ruby: the native extension survives a compacting garbage collection. It kept a handful of Ruby objects (Success, Fault, Date, DateTime, Decimal and the two built-in formats) in a Rust static that a compacting GC could move. A door called after a compaction could then segfault. Each is now pinned when the extension loads, and a new spec compacts before calling a door.
  • Go: a build with no cgo (CGO_ENABLED=0) once again fails with only the one explanatory error, instead of also reporting undefined: symChar.

Upgrade notes

Source-compatible for every consumer except one narrow case.

  • Java: a call that passed a literal null as the format, such as Cast.i32(text, null), no longer compiles, because the new Locale overload makes it ambiguous. It already threw at run time, so no working code depends on it.
  • Rust: version = "0.8" in Cargo.toml. Under 0.x caret rules, "0.7" never resolves to 0.8.

Verifying

Every native library and static archive in this release is built by CI and carries a build-provenance attestation, the new Android libraries and archives included:

gh attestation verify go/staticlib/android_arm64/libhypercast.a \
  --repo SkunkWerkx/HyperCast --signer-repo SkunkWerkx/.github

The gems and Python wheels are attested by this repo's own release.yml, so --repo SkunkWerkx/HyperCast alone verifies them.