Nova — конверсии типов

Сводная страница всех правил конверсии в одном месте. Полные D-decisions: D54, D73, D77.


Три механизма

МеханизмКогдаПример
asinfallible numeric/newtype/sum cast42 as f64, n as i16
T.from(v) / v.into()infallible struct/format конверсияstr.from(42), c.into()
T.try_from(v)?fallible parsing/validationint.try_from("42")?

Auto-derive 4-way (D73): пишешь одну форму — компилятор синтезирует три остальные.

  • T.from(v V)v.@into() -> T
  • T.try_from(v V)v.@try_into() -> Result[T, E]

Numeric ↔ numeric

Widening (no precision loss)

From → ToЧерезСемантика
i8 → i16/i32/i64/intassign-extend
u8 → u16/u32/u64/intaszero-extend
i8/u8 → f64asexact (любой int64 representable как f64)
f32 → f64asexact

Narrowing (potential precision loss)

From → ToЧерезСемантика
i64 → i32/i16/i8aswraparound (modulo 2^N)
u64 → u32/u16/u8/byteaswraparound
f64 → f32asIEEE rounding (потеря точности)
f64/f32 → iN/uNassaturation + NaN→0 + ±∞→bounds

Float→int saturation — defined behavior на любом входе (отличие от C/C++ UB). Согласовано с Rust 1.45+.

let n = 1e20 as int             // saturates to INT64_MAX
let m = (-1.0) as u32           // saturates to 0
let nan = 0.0 / 0.0 as i16      // 0

Numeric ↔ str

str → numeric (parse, fallible)

From → ToЧерезFailure
str → int/i64int.try_from(s)?non-digit / overflow
str → i8/i16/i32i32.try_from(s)?+ range out-of-bounds
str → u8/u16/u32/u64u32.try_from(s)?+ negative / overflow
str → f64/f32f64.try_from(s)?invalid number format
let n = int.try_from("42")?     // Ok(42)
let m = int.try_from("abc")     // Err
let f = f64.try_from("3.14")?   // Ok(3.14)

numeric → str (format, infallible)

From → ToЧерез
int/iN/uN → strstr.from(n)
f64/f32 → strstr.from(f)
byte → strstr.from(b)
let s = str.from(42)            // "42"
let f = str.from(3.14)          // "3.14"

Char / Byte / []byte / str

char → str (UTF-8 encode)

ЧерезСемантика
str.from(c char)infallible UTF-8 encode (1-4 байта)
c.into() -> strauto-derived из str.from(char)

str → char (single codepoint, fallible)

ЧерезFailure
char.try_from(s str)?empty / multi-char / invalid UTF-8

int → char (codepoint range-check, fallible)

ЧерезFailure
char.try_from(n int)?n < 0 / n > 0x10FFFF / surrogate

char → byte (only if codepoint < 256, fallible)

ЧерезFailure
byte.try_from(c char)?codepoint > 0xFF

Исключение: 'A' as byte, 'A' as int, 'A' as u8 — разрешены для char-литералов (compile-time-known codepoint).

[]byte ↔ str

From → ToЧерезFailure
str → []bytebytes() методinfallible (UTF-8 уже валиден)
[]byte → strstr.try_from(bs []byte)?invalid UTF-8

Bool ↔ всё

From → ToЧерезСемантика
bool → intastrue=1, false=0
bool → byte / bool → f64asто же
bool → strstr.from(b)"true" / "false"
str → boolbool.try_from(s)?match "true"/"false" strict
int/byte/f64/etc → boolзапрещеноuse n != 0
let s = str.from(true)          // "true"
let b = bool.try_from("true")?  // Ok(true)
let n = if x != 0 { ... }       // explicit

Newtype ↔ underlying

ЧерезСемантика
n as MyNewtypeidentity (одинаковое C-представление)
nt as intidentity
type UserId alias int
let u UserId = 42 as UserId
let n int = u as int            // 42

Sum-variant ↔ int (discriminant)

Для sum’ов с числовыми discriminants:

type ErrorCode | NotFound = 404 | InternalError = 500
let code = NotFound as int      // 404

int → Sum через as запрещён (число может не попасть в варианты). Используй pattern match.


Strict if cond:bool / while cond:bool

if cond, while cond, cond1 && cond2, cond1 || cond2cond обязан быть bool. Truthy-int (if a где a: int) запрещён.

let n int = 5
if n { ... }                    // ❌ compile error
if n != 0 { ... }               // ✅

Прецеденты: Rust, Swift, Kotlin — все требуют bool. Python/C/JS — truthy, известный bug-class.


Запрещённые конверсии — таблица

Запрещено через asАльтернатива
int as char, iN/uN as charchar.try_from(n)?
char as byte (кроме CharLit)byte.try_from(c)?
int/byte/f64 as booln != 0
str as int/i32/f64/boolT.try_from(s)?
int/f64/bool/char as strstr.from(v)
T as U для произвольных typesU.from(v) или U.try_from(v)?
if int_valueif n != 0

Auto-derive 4-way

D73 — программист пишет одну форму конверсии, компилятор даёт три:

// Программист пишет одну:
fn Celsius.try_from(n int) -> Result[Celsius, str] => ...

// Компилятор синтезирует:
fn int @try_into() -> Result[Celsius, str] => Celsius.try_from(@)

Bootstrap-status: реализовано в codegen (Plan 08 Ф.3) для пары try_fromtry_into и frominto. Транзитивный auto-derive (A.from(B) + B.from(C)A.from(C)) не делается — каждая пара регистрируется явно.


Прецеденты по языкам

ЯзыкГде близок к Nova
Rustas semantics, From/Into pair, char::from_u32
Swiftstrict bool, no implicit coerce, Int(throwing:)
Kotlinstrict if-cond:bool, .toInt()/.toIntOrNull()
Go_ = strconv.ParseInt(s) ≈ try_from
Pythonstr(x)/int(s) ≈ from/try_from но не type-safe
C/C++(int)x без проверок — UB-class, Nova не повторяет

Bootstrap status (2026-05-08)

Реализовано в bootstrap-codegen:

  • ✅ Plan 05: as-cast как явный C-cast (narrowing wraparound для int)
  • ✅ Plan 07: float→int saturation (defined на NaN/Inf/out-of-range)
  • ✅ Plan 08 Ф.1+Ф.2: runtime helpers + bootstrap-table для int/f64/bool/char ↔ str
  • ✅ Plan 08 Ф.3: 4-way auto-derive synthesis (try_from ↔ try_into, from ↔ into)
  • ✅ Plan 08 Ф.4: strict if cond: bool (codegen check)
  • ✅ Plan 08 Ф.5: as-cast restrictions для char/byte/bool

Не реализовано (отложено):

  • ❌ Plan 08 Ф.6: generic-bound [T Into[X]] enforcement в type-checker
  • ❌ Транзитивный auto-derive (consciously)
  • ❌ Compile-error suggestions с file:line:col (TBD)
  • ❌ char as raw-bytes / lossy unicode conversions

Ссылки