Choosing numeric types¶
This how-to guide helps you choose numeric annotations in new Incan code.
For exact rules, see Numeric semantics. For the design rationale, see Why numeric types work this way.
Coming from Python?
Python usually lets you start with int and think about range only when a library boundary complains. Incan has more numeric spellings because it needs to model Rust APIs, data files, database schemas, analytics engines, and fixed-scale decimal values without hidden casts.
Start with int and float for ordinary Incan-owned logic, then choose a narrower, wider, unsigned, decimal, or schema-shaped alias only when the representation is part of the contract you are writing against.
Use int and float for ordinary Incan-owned code¶
Use int for ordinary whole-number logic and float for ordinary binary floating-point logic.
retries: int = 3
timeout_seconds: float = 2.5
Do not pick i8, i16, u8, or u16 just because today's values are small. Pick exact widths when the width is part of an external contract or storage format.
Match exact widths at external boundaries¶
Use exact-width types when a Rust API, data file, wire format, FFI boundary, or schema owns the representation.
model EncodedHeader:
version: u8
flags: u16
payload_len: u32
Convert inward if the rest of the program does not need the exact width.
model RawEvent:
sensor_id: u32
sequence: u64
model Event:
sensor_id: int
sequence: int
Use schema-shaped aliases in schema-shaped code¶
Use aliases when they keep a database, analytics, or interchange schema readable.
model WarehouseRow:
id: bigint
category_id: integer
priority: smallint
score: double
Aliases canonicalize to exact Incan types.
| Alias | Canonical type |
|---|---|
smallint, short |
i16 |
integer |
i32 |
int, bigint, long |
i64 |
hugeint |
i128 |
real, fp32 |
f32 |
float, double, fp64 |
f64 |
numeric[p, s] |
decimal[p, s] |
Use canonical names when the exact width is the important thing. Use aliases when matching source vocabulary matters more.
Match Rust numeric parameters instead of relying on casts¶
When a Rust function expects a primitive width, annotate the Incan value at that width near the call boundary.
from rust::metrics_core import record_code
code: i32 = 200
record_code(code)
Lossless widening is accepted.
from rust::metrics_core import record_total
count32: i32 = 100
record_total(count32)
Narrowing is rejected. Pick a policy before calling the Rust function.
from rust::metrics_core import record_code
count: int = 200
maybe_code: Option[i32] = count.try_resize()
match maybe_code:
case Some(code): record_code(code)
case None: println("count is outside i32")
Use decimals for fixed-scale values¶
Use decimal[p, s] or numeric[p, s] when base-10 precision and scale are part of the value.
unit_price: decimal[12, 2] = 19.99d
tax_rate: numeric[6, 4] = 0.0825d
Precision is the total digit budget. Scale is the fractional digit budget.
ok: decimal[6, 2] = 1234.56d
bad_scale: decimal[6, 2] = 123.456d
bad_precision: decimal[6, 2] = 12345.67d
Decimal arithmetic is not general language behavior yet. Use decimals today for typed boundaries, literal validation, formatting, generated Rust, and display.
Pick a resize policy before narrowing¶
Use resize() only when the conversion is lossless.
small: i8 = 120
wide: int = small.resize()
Use try_resize() when a value may not fit and failure should be data.
incoming: int = 240
maybe_small: Option[i8] = incoming.try_resize()
Use wrapping_resize() only when modulo-width behavior is intended.
raw: u16 = 258
byte: u8 = raw.wrapping_resize()
Use saturating_resize() when clipping to the target range is intended.
sample: i16 = 500
clipped: i8 = sample.saturating_resize()
Avoid unsigned integers as validation¶
Unsigned types describe representation. They are not a replacement for checking user input or business rules.
def set_retry_count(count: int) -> Result[int, str]:
if count < 0:
return Err("retry count cannot be negative")
return Ok(count)
Use unsigned types when the boundary is genuinely unsigned.
model EncodedHeader:
byte_len: u32
checksum: u64
Review checklist¶
- Does another system own the width or precision?
- Does this type appear in a public API, file format, Rust call, or schema?
- If the conversion can lose data, is the policy explicit in source?
- Would
int,float, ordecimal[p, s]be clearer for Incan-owned logic? - If using
usize, is the value actually tied to a Rust or platform-sized API?