Integers

An integer type holds a whole number in a fixed number of bits. The signed types int8 to int512 use two's complement; the unsigned types uint8 to uint512 hold zero and up. int and uint are as wide as a pointer.

The integer types

TypeBitsBytesMinMaxSuffix
int881−128127i8
int16162−32,76832,767i16
int32324−2,147,483,6482,147,483,647i32
int64648−263 ≈ −9.22 × 1018263 − 1i64
int12812816−2127 ≈ −1.70 × 10382127 − 1i128
int25625632−2255 ≈ −5.79 × 10762255 − 1i256
int51251264−2511 ≈ −6.70 × 101532511 − 1i512
int648as int64as int64i
uint8810255u8
uint16162065,535u16
uint3232404,294,967,295u32
uint646480264 − 1 ≈ 1.84 × 1019u64
uint1281281602128 − 1 ≈ 3.40 × 1038u128
uint2562563202256 − 1 ≈ 1.16 × 1077u256
uint5125126402512 − 1 ≈ 1.34 × 10154u512
uint6480as uint64u

byte is a built-in alias of uint8, for values that are raw storage rather than numbers.

int and uint are pointer-sized: their width follows the target, and it is 64 bits on every target rux 0.4.0 supports. int is the type of an unsuffixed integer literal and of Main's exit status; uint is the type of lengths, indices and sizeof. When a value must keep a width across targets — a file format, a protocol, a C structure — name a fixed-width type.

The 128-, 256- and 512-bit types are ordinary integers: they support every operator and conversion below.

Associated constants

Every integer type has four constants. They are declared in the standard Core package, so the type has to be imported from it, and Core listed under [Dependencies]:

ConstantTypeValue
BitsuintWidth in bits
BytesuintStorage size in bytes
Minthe typeSmallest value
Maxthe typeLargest value
import Core::{ int, int8, uint64 };
import Io::PrintLine;

func Main() -> int {
    PrintLine("int8   {} to {}", int8::Min, int8::Max);   // -128 to 127
    PrintLine("uint64 max {}", uint64::Max);              // 18446744073709551615
    PrintLine("int    {} bits", int::Bits);               // 64
    return 0;
}

Without the import, the constant is not found: error: 'Max' not found in extend for type 'int8'. An alias carries the constants of its target, so byte::Max is 255 once byte is imported.

Literals

Integer literals are decimal, hexadecimal (0x), octal (0o) or binary (0b), with _ between digits, and an optional suffix from the table above. An unsuffixed literal takes the type its context requires — the declared type it initialises, or the other operand's type — and is an int otherwise. It must fit that type:

let level: uint8 = 200;
let raised = level + 100;   // 100 is a uint8
let mask = 0xFF00u16;

let tooBig: uint8 = 256; is error: integer literal is out of range for type 'uint8'. Literals gives the full spelling rules.

Operations

OperatorsResultRules
+ - *The operand typeWrap around modulo 2bits
/ %The operand typeTruncate toward zero; panic on a zero divisor
prefix -The operand typeWraps; on an unsigned type, gives 2bits − x
& | ^ ~The operand typeBitwise
<< >> >>>The left operand's typeSee Shift
== != < <= > >=boolNumeric order
++ -- and compound assignment—As the operator they stand for

Wrapping

+, - and * wrap: a result that does not fit keeps its low bits, in every build profile.

var level: uint8 = 250;
level += 10;        // 4

var small: int8 = 127;
small += 1;         // -128

When overflow must be detected, use the Core functions AddChecked, SubChecked and MulChecked, which report it, or AddSaturating and its siblings, which stop at the limits:

import Core::{ AddChecked, AddSaturating };
import Io::PrintLine;

func Main() -> int {
    let level: uint8 = 250;
    var sum: uint8 = 0;
    if AddChecked(level, 10u8, @sum) {
        PrintLine("overflow; wrapped to {}", sum);       // 4
    }
    PrintLine("saturated: {}", AddSaturating(level, 10u8));   // 255
    return 0;
}

Division

/ truncates toward zero and % takes the sign of the dividend: -7 / 2 is -3, and -7 % 2 is -1.

A zero divisor stops the program with a panic, on every target and in every build profile. So does the one signed quotient that does not fit, a type's Min divided by -1, and the matching %:

Panic: division by zero
  at Divide (Src/Main.rux:5:14)

Panic: division overflow
  at Main (Src/Main.rux:16:23)

Mixed operands

Two operands of the same signedness meet at the wider type: an int32 plus an int64 is an int64. Operands of different signedness are an error, since either conversion could change a value — convert one with as to choose:

error: operator '+' cannot combine left operand 'uint64' with right operand 'int64'
error: operator '<' cannot compare left operand 'uint64' with right operand 'int64'

Conversions

An integer widens implicitly to a wider integer of the same signedness, and int and int64 (likewise uint and uint64) convert to each other freely. Everything else is written with as:

ConversionResult
To a narrower integerKeeps the low bits: 300 as int8 is 44
Between signed and unsignedKeeps the bit pattern: -1 as uint8 is 255
To a wider integerSign-extends a signed value: (-1i32) as uint64 is 264 − 1
To a floating-point typeThe nearest representable value
To a boolean typefalse for zero, true for anything else
To a character typeThe character with that code; see Characters
let big: int32 = 300;
let narrowed = big as int8;        // 44
let negative: int32 = -1;
let unsigned = negative as uint8;  // 255
let ratio = 7 as float64 / 2.0;    // 3.5
let flag = 256 as bool;            // true

A narrowing as never fails; it produces a different value. To find out whether a value fits first, compare it with the target's Min and Max, or call Core::ConvertChecked, which returns true when the conversion lost the value.

Implicit widening refuses what it cannot do losslessly: let d: int16 = c; for a uint8 c is error: cannot assign 'uint8' to 'int16', and let i: int32 = n; for an int n is error: cannot assign 'int' to 'int32'.

See also