Numbers
Djinn provides fixed-width integer and floating-point types, native-width aliases, and per-type overflow behavior via type/literal suffixes.
Integer Types
Integers are iN (signed) and uN (unsigned) for widths 8, 16, 32, 64 and 128.
i8 a = 127;
u32 b = 4000000000;
i64 c = 800'000'000'000;Number literals support _ and ' separators, scientific notation (1e9), hex (0xFF) and
binary (0b1010) notation.
Native-Width Types
nint— signed integer with the target platform's pointer width (i64on 64-bit targets,i32on 32-bit). Ideal for sizes, indexes and FFI with Cptrdiff_t/intptr_t.nfloat— the platform's Cfloat(IEEE 754 single precision,f32).ndouble— the platform's Cdouble(IEEE 754 double precision,f64).
nint index = 0;
nfloat ratio = 1.5;
ndouble precise = 2.718281828;Native types are display names: typeof and diagnostics show nint, and they interoperate
freely with their fixed-width equivalents (i64, f32, f64).
Overflow Modes
Plain integer arithmetic wraps silently on overflow (C-style). Every integer type — and integer literals — accept a suffix that selects what happens when an operation overflows:
| Suffix | Mode | Behavior on overflow |
|---|---|---|
w | wrapped | Two's complement wrap (the default, made explicit) |
t | trapped | Runtime panic — aborts the program |
c | checked | Raises the builtin Overflow error (requires a throws function) |
s | saturating | Clamps the result to MIN_VALUE/MAX_VALUE |
i32 common = 123;
i32w wrapped = 121231234w; // wraps like C
i32t trapped = 232134324t; // panics on overflow
i32c checked = 232343c; // throws Overflow
i32s saturation = 23498s; // clamps to i32.MAX_VALUEThe mode is a property of the operation's operands: the left operand's mode wins, and the right one applies when the left has none. Conflicting explicit modes are a compile error.
i32w a = 1000w;
i32w b = a + 23; // wrapping add (mode from `a`)
i32w c = 2000w + 3w; // wrapping add (mode from the literals)
i32s d = 2000000000s;
i32s e = d + d; // saturates at i32.MAX_VALUE = 2147483647Checked overflow
The c mode raises the language-level builtin Overflow error through the standard error
handling mechanism, so the enclosing function must declare throws:
i32 safeAdd(i32c a, i32c b) throws {
return a + b; // sets the error flag instead of wrapping
}
i32 main() throws {
i32 result = safeAdd(2147483647c, 1c) ?: -1; // try/fallback
return result;
}Compile-time behavior
Overflow rules also apply to literals and constexpr/consteval expressions:
- An out-of-range literal is a compile error for
tandctypes - Saturating literals clamp at compile time (
i32s a = 3000000000s;becomes2147483647) - Wrapped literals keep the C-style truncation
i32t bad = 3000000000t; // error: integer literal overflows 'i32t'
i32s clamped = 3000000000s; // ok: value is i32.MAX_VALUE
i32w wrapped = 3000000000w; // ok: wraps to -1294967296What is covered
The +, -, *, /, % operators, unary negation (-x) and ++/-- all honor the overflow
mode — including the INT_MIN / -1 edge case for division and remainder.
Non-Zero Types
The n suffix declares an integer type that cannot hold the value 0. The domain of iNn is
MIN..-1 plus 1..MAX; the domain of uNn is 1..MAX. Non-zero types compile to the same
representation as their base type — the guarantee is enforced by the compiler:
- Assigning the literal
0(including0x0,0b0) is a compile error - Implicit conversion from a plain integer type is rejected — the value might be zero; use an explicit cast, which is trusted and unchecked at runtime
- Non-zero values convert implicitly to the plain type
- Arithmetic results lose the guarantee (
a - ais zero), soi32n + i32nproducesi32 ++/--are rejected on non-zero variables (the result may be zero), and a non-zero variable must be initialized when declared
u32n divisor = 10; // ok
u32n bad = 0; // error: integer literal 0 is not assignable to non-zero type 'u32n'
i32 plain = 5;
i32n fromPlain = plain; // error: cannot implicitly convert 'i32' to 'i32n'
i32n trusted = (i32n)plain; // ok: explicit cast is trusted
i32n a = 5;
i32 sum = a + a; // ok: result type is i32 (non-zero-ness dropped)
i32 back = a; // ok: non-zero flows into the plain type freelyThe suffix combines with overflow suffixes in either order: i32nt/i32tn is a trapped
non-zero 32-bit signed integer.
Contracts
Non-zero types integrate with the contracts system in both directions:
- Non-zero parameters are implicit
require(p != 0)clauses. The callee checks the parameter at entry (a violation throwsContractViolation), and with compile-time error enforcement a call passing a constant0is rejected by the compiler (9007). Inside the body the parameter carries the guarantee, so divisions by it skip the zero-divisor check. require(p != 0)upgrades a plain parameter to non-zero for the whole body: it can be assigned toi32nvariables and used as a proven non-zero divisor. The explicit require is folded into the single non-zero entry check.ensure(return != 0)upgrades the return type to non-zero for callers, regardless of declaration order, soi32n x = try f() ?: 1;compiles (functions with contracts are throwing, hence thetry).
i32 safeDiv(i32n b) {
return 100 / b; // no zero-divisor check: b is contract-guaranteed non-zero
}
i32 legacyDiv(i32 b)
require(b != 0) // b is treated as i32n inside the body
{
return 100 / b;
}
i32 five()
ensure(return != 0)
{
return 5;
}
i32 main() {
i32n x = try five() ?: 1; // ok: ensure proves the result non-zero
safeDiv(0); // compile error: passes zero to non-zero parameter 'b'
return 0;
}Division and remainder by a proven non-zero divisor (non-zero literal, i32n variable,
contract-proven parameter, or a cast to a non-zero type) emit a plain sdiv/srem with no
runtime zero check.