Repository navigation
v0.9.0 - the culture-fidelity sweep, free-threaded Python, and Blazor fixes
0.9.0 is the culture-fidelity sweep planned in the 0.8.0 roadmap: everything a culture declares beyond its separators now crosses into the core, in one coordinated ABI change across the core and all seven bindings. One rule throughout: the ASCII or English default is always read, and a declared symbol is a further spelling read beside it, so no input that parsed in 0.8.0 reads differently. Also free-threaded Python, and the Blazor WebAssembly fixes from Norse Architecture's integration. The full list, with a bullet per binding, is in CHANGELOG.md.
Highlights
-
Every numeric format carries what a culture declares.
NumFormatgains, beside the separators, flags and currency symbol it had:minus_signandplus_sign(U+2212 for Estonian and Basque; an RTL mark and a hyphen).currency_decimal_sepandcurrency_group_sep, read in place of the number separators once the declared currency symbol is found, as the BCL switches when it sees the symbol (en-DEwrites1.234,5and€1,234.50).percent(٪,%) anddigits, the zero of a native digit block read beside ASCII (١٢٣,123).- A separator may now be one or two characters.
- A new lenience,
TRAILING_SIGN, reads the sign after the digits (5-,5 €-,2.5%-). It is not part ofALL; every bridge sets it from the culture's own negative pattern.
corpus/integer.json,real.jsonanddecimal.jsonpin every row, and every suite replays them. -
Day-period designators beside the declared date order. The datetime door reads a culture's own AM/PM words (
오전/오후,vorm./nachm.,a. m./p. m.) after the clock or, as Korean and Chinese write them, ahead of it, beside the English pair. The C ABI'scast_datetimetakes aRawDayPeriodspointer after the order, null for English only.corpus/datetime.jsonpins it. -
Every binding takes the whole declaration, and every bridge fills every field its platform has.
Language Numeric format Day periods Bridge Rust with_signs,with_currency_separators,with_percent,with_digits;Symbol<8>separatorscast_datetime_with(text, order, &DayPeriods)— C# init-only MinusSign,PlusSign,CurrencyDecimalSeparator,CurrencyGroupSeparator,PercentSymbol,DigitZero(Rune?);NumStyles.TrailingSignDateFormat(order, am, pm);Cast.DateTimeoverloads forDateFormatandIFormatProvider?NumFormat.FromandDateFormat.Fromread every field fromNumberFormatInfoandDateTimeFormatInfo; neither throws on any runtime's ICU or NLS dataJava with*for each field;STYLE_TRAILING_SIGNDateTimeFormat(order, am, pm);Cast.dateTimeoverloads forDateTimeFormatandLocaleNumFormat.from(Locale)and newfrom(DecimalFormat); `DateSwift minusSign,plusSign,currencyDecimalSeparator,currencyGroupSeparator,percentSymbol,digitZero;.trailingSignDateFormat(order:amSymbol:pmSymbol:);dateTime(_:format:)anddateTime(_:locale:)NumFormat.from(locale:)and newfrom(formatter:);DateFormat.from(locale:)PHP Named constructor arguments; NumFormat::TRAILING_SIGNnew DayPeriods($am, $pm)as the third argument ofCast::datetimefromLocaleconvandfromNumberFormatterread the monetary separators, signs, percent, zero digit and negative suffixRuby Keywords on the Data;HyperCast::TRAILING_SIGNHyperCast.datetime(text, order, am:, pm:)NumFormat.from_i18nreadsnegative_formatand takescurrency_format:Python Keyword-only constructor arguments; NumFormat.TRAILING_SIGNcast_datetime(text, order, am=, pm=)NumFormat.from_localeconvreads the monetary separators and signsThe boolean door stays English, by decision: no platform's culture data carries a boolean lexicon to bridge. A feed that says
ja/ouimaps its words before the door. -
The format crosses the C ABI as a new, size-tagged layout.
RawNumFormatis a size word followed byNumFormatitself, now#[repr(C)]: 92 bytes, read by the core in place, so a cast pays for a check and no copy.RawDayPeriodsis 40 bytes.rust/src/abi.rshas the offset table, and every binding restates it and pins it in its interop tests. Measured through the C ABI, a groupedcast_i32under a declared$went from 21.9 ns on the 0.8.0 core to 19.6 ns: the sweep costs nothing at the crossing. In Rust,decimal_sepandgroup_separeSymbol<8>where they werechar;NumFormat::newstill takeschars. -
Python: free-threaded CPython, with the GIL off. Each platform gets three wheels where it had one:
cp311-abi3for CPython 3.11+ with the GIL as before,cp314-cp314tfor free-threaded 3.14, andcp315-abi3.abi3t, PEP 803's stable ABI for 3.15+ with or without the GIL. The module declares it needs no GIL, so a free-threaded interpreter keeps it off afterimport hypercast, and every door is safe to call from many threads at once. PyPI gets 25 wheels per release where it got 9. A build from a checkout needs maturin 1.15 or newer, and the crate'spython-valuesfeature is now built on PyO3 0.30. -
C#: Blazor WebAssembly. A publish without the
wasm-toolsworkload used to skip linking the native core and ship a runtime with nocast_*symbols, with nothing louder than the SDK's own warning. The package's targets now fail the build withHYPERCAST002; an app that gates onCast.IsAvailableand keeps a managed fallback setsHyperCastRequireWasmWorkloadtofalse. The browser-wasm archive moves toruntimes/browser-wasm/nativeassets/net11.0/, matching the only framework that links it, and it now carries build provenance, which it did not in 0.8.0. -
Benchmark rows for a declared currency symbol and for rejection, in every binding. A declared symbol costs between nothing measurable (Python, PHP) and 11 ns (Go). A malformed value is faster than the platform's own failing parse wherever that parse throws: 4.6x in Python, 16x in Java.
Fixed
- C#:
hyper-pack-nuget.ymlnow verifies every native it packs and fails the release on one without provenance, which is how the unattested browser-wasm archive in 0.8.0 went unnoticed. - C# README: the generic
ParseRequired<T>example did not compile (CS8780). It now uses theTryGetValuechain. - **P
ffi.enable=preloadthe first numeric door failed with "FFI API is restricted". It now preloads both directories.
Upgrading
- Rust:
version = "0.9"inCargo.toml. A crate usingpython-valuesmoves to PyO3 0.30 with it; Cargo refuses two PyO3 versions in one build. - Every binding:
NumFormat's separator fields are strings where they were a single character. - PHP:
NumFormat::codePoints()is removed;packed()returns the 92-byte layout. The per-callfromNumberFormattermemo now reads twelve attributes and costs about four times a hoistedNumFormat, so hoist it. - Swift:
from(locale:)derives afresh for.currentand.autoupdatingCurrentso a user's overrides are honoured; a hot loop passes an explicit locale or a heldNumFormat. - C#: the browser-wasm archive's path moved to
net11.0. The package's targets name it, so nothing a consumer sets changes.
Verifying
Every native library and static archive in this release is built by CI and carries a build-provenance attestation, the browser-wasm archive now included:
gh attestation verify runtimes/browser-wasm/nativeassets/net11.0/libhypercast.a \
--repo SkunkWerkx/HyperCast --signer-repo SkunkWerkx/.githubThe gems and Python wheels are attested by this repo's own release.yml, so --repo SkunkWerkx/HyperCast alone verifies them.