Rust 1.98 přidal operace s desetinnými čísly, u kterých smí překladač měnit pořadí
Rust 1.98 vyšel 20. srpna 2026 a přinesl pětici metod, u kterých překladač nemusí dodržet pořadí, v jakém jsou operace napsané. Povolení se dává po jednotlivých řádcích, ne přepínačem pro celý překlad. V měření, ze kterého ten návrh vzešel, zrychlil skalární součin z 84 na 10 mikrosekund.

Rust 1.98 vyšel 20. srpna 2026 a nese pět metod, které v jazyce dosud nebyly: algebraic_add, algebraic_sub, algebraic_mul, algebraic_div a algebraic_rem. Dostaly je typy f32 i f64. Sečíst jimi dvě čísla jde stejně jako operátorem plus, jenže překladač u nich smí sáhnout na pořadí.
Proč překladač desetinná čísla sám nezrychlí
Optimalizace stojí na slibu, že se výsledek nezmění. U celých čísel má překladač volnou ruku, protože a + (b + c) se rovná (a + b) + c. Součet pole tak jde rozdělit na několik proudů a nechat procesor sečíst čtyři hodnoty jedinou instrukcí.
Desetinná čísla tuhle rovnost nesplňují. Jejich rozsah je tak velký, že dost malé číslo přičtené k dost velkému zanikne: 1e16 + 1.0 se rovná 1e16. Pořadí sčítání proto výsledek mění. Pole s milionem jedniček, které má hodnotu 1e16 na začátku, dá jiný součet než totéž pole s tou hodnotou na konci. Překladač předpokládá, že si autor kódu pořadí vybral schválně, a nesáhne na ně.
Kolik ta opatrnost stojí, změřil Itamar Turner-Trauring na procesoru Intel Core i7-12700K. Milion hodnot typu i64 jeho program sečetl za 151,9 mikrosekundy, milion hodnot f64 za 595,2 mikrosekundy. V prvním případě procesor 250 003krát sáhl po 256bitové instrukci SIMD, ve druhém ani jednou.
Povolení se dává po jednotlivých operacích
Nové metody dělají totéž co běžné operátory, ale říkají překladači, že u nich smí využít algebraické vlastnosti reálných čísel. V plovoucí řádové čárce ty vlastnosti neplatí, a právě proto se jinak nepoužívají. Součet napsaný jako řetěz volání algebraic_add tedy překladač smí spočítat po dvojicích a obě poloviny naráz.
let mut total: f64 = 0.0;
for value in values {
total = total.algebraic_add(*value);
}
Jaké úpravy si přitom dovolí, poznámky k vydání neupřesňují. Podobat se to prý bude tomu, co v jiných jazycích udělá přepínač -ffast-math. Rozdíl je v dosahu: přepínač platí pro celý překlad, kdežto tady se povolení dává řádek po řádku.
Výsledek je nedeterministický. Tytéž vstupy mohou vyjít pokaždé jinak i uvnitř jednoho běhu programu, protože si překladač smí vybrat jinou optimalizaci. Nedefinované chování z toho ale nevznikne a dokumentace k tomu dodává, že se nebezpečný kód nesmí spoléhat na žádnou vlastnost návratové hodnoty.
Osmkrát pomalejší skalární součin
Celá změna vznikla z jednoho měření v roce 2025. Skalární součin dvou polí, tedy jedna z nejběžnějších úloh numerického kódu, byl ve stabilním Rustu proti C++ zhruba osminásobně pomalejší. Autor návrhu to změřil na notebookovém procesoru Intel Core i7-10875H. Kód v C++ přeložený Clangem 18.1.3 potřeboval 10 mikrosekund, tentýž algoritmus v Rustu 1.84.1 osmdesát čtyři. Rust v kanálu nightly, kde už tehdy odpovídající vnitřní operace existovaly, se vrátil na 10 mikrosekund. Testy jsou v repozitáři dot-bench.
Součet po dvojicích potřebuje obojí
K čemu je to jemné dávkování dobré, ukázal Turner-Trauring na součtu po dvojicích. Je to algoritmus, kterým sčítá pole i knihovna NumPy: pole se rozpůlí, každá půlka se sečte rekurzivně týmž postupem a oba mezisoučty se sečtou nakonec. Pod hranicí 128 prvků se sčítá postupně a právě tam dal algebraické sčítání. Ty dva mezisoučty nahoře nechal na obyčejném plusu, aby překladač na jejich pořadí nesáhl.
Milion hodnot mu tak sečetl za 144,5 mikrosekundy. Funkce np.sum potřebovala na tomtéž poli 190,7 mikrosekundy a prosté sčítání v cyklu 563,1. Přesnost přitom zůstala na úrovni NumPy. U pole, které začíná hodnotou 1e16, minul prostý součet správný výsledek o milion, NumPy o čtrnáct a součet po dvojicích o šest.
Druhý jeho test počítal součet druhých mocnin rozdílů dvou polí. Přechod na algebraické operace ho zkrátil z 628,7 na 371,1 mikrosekundy a počet instrukcí procesoru na jednu hodnotu klesl ze 4,5 na jednu. Měření vzniklo osmnáct dní před vydáním, takže autor kód pouštěl v kanálu beta, který podle něj obsahuje tutéž funkci.
Kdyby algebraické operace stály ve všech řádcích algoritmu, překladač by ho mohl zjednodušit na obyčejný cyklus a přesnost, kvůli které vznikl, by zmizela. Kdyby tam nebyly vůbec, kód by běžel pomaleji. Míchat rychlost a přesnost uvnitř jedné funkce je podle nás na celé změně to nejzajímavější.
Formátování čísel bez cizí knihovny
Druhá novinka vydání míří na výpis čísel. Celočíselné typy dostaly metodu format_into, která zapíše dekadický zápis do bufferu NumBuffer a vrátí kus řetězce s vypůjčenou životností. Buffer je dost velký na jakoukoli hodnotu daného typu a metoda obchází většinu práce navíc, kterou formátování přes makro write! odvádí kvůli dynamickému výběru implementace. Podle poznámek k vydání vychází format_into v testovací sadě itoa-benchmark podobně jako knihovna itoa, takže ji projekty můžou přestat používat.
Stabilizovalo se i pár drobností. Metody str::substr_range a [T]::subslice_range zjistí, kde přesně v původním řetězci nebo řezu leží jeho část. Funkce String::from_utf16le a její příbuzné převádějí UTF-16 s určeným pořadím bajtů a atomické typy dostaly from_mut a get_mut_slice.
Zápis staré opravy do dokumentace
Poslední z hlavních položek novinka není. Do verze 1.96 platilo, že přesun hodnoty ManuallyDrop<Box<_>> poté, co se box uvolnil, je nedefinované chování. Způsobovala to chyba v překladači. Verze 1.96 ji opravila a 1.98 to dopsala do dokumentace jako závazek, že takový kód nedefinované chování mít nebude ani napříště. Souvislosti popisuje RFC 3336.
Do stabilního kanálu naopak zatím nedorazila nová podoba borrow checkeru, kterou Rust zapnul v nightly začátkem srpna. Tým ji chce stabilizovat do konce roku.
Zdroje: oznámení vydání na blogu Rustu, návrh ACP 532 v repozitáři knihovního týmu Rustu, měření Itamara Turner-Trauringa a zpráva Phoronixu.