Peeper

Numbers and casts

Integers of any width, floats, how values widen, and casts with as.

Number types

  • i and u with a width give signed and unsigned integers of any width from 1 bit up to 2^23 bits: i8, u24, i200, and so on.
  • usize is as wide as a pointer on the target machine.
  • f32 and f64 are floats.

A literal takes the type it is used as, or the one its suffix names. _ can separate digits.

const Bits: u8 = 1u8 << 7;          // 128
const Big: i128 = 1i128 << 100;
const Odd: u24 = 0xFF_FFFF;         // hex, with `_` between digits

Widening

A value widens on its own when the new type can hold every value of the old one, so a u8 goes into an i16 or a u32 without a cast. Any other change needs as. The result of an operator has the type of its left operand.

let small: u8 = 200;
let signed: i16 = small;
let unsigned: u32 = small;
examples/02_numbers.peep
// Numbers: signed `i` and unsigned `u` integers of any width from 1 to 2^23
// bits (`i8`, `u24`, `i200`, ...), `usize` as wide as a pointer, and `f32`
// and `f64` floats. A literal takes the type it is used as, or the one its
// suffix names; constants fold while compiling.
// Run: peeper run examples/02_numbers.peep
const Bits: u8 = 1u8 << 7;          // 128
const Big: i128 = 1i128 << 100;
const Odd: u24 = 0xFF_FFFF;         // hex, with `_` between digits
const Half: f32 = 0.5f32;

fn main() {
    println("constants:", Bits, Big, Odd, Half);

    // A value widens on its own when the new type holds every value of the
    // old one; anything else needs `as` (examples/03_casts.peep).
    let small: u8 = 200;
    let signed: i16 = small;
    let unsigned: u32 = small;
    let float: f64 = Half;
    println("widened:", signed, unsigned, float);

    // The result of an operator has the left operand's type.
    let x: i32 = 17;
    println("arithmetic:", x + 3, x - 20, x * 2, x / 5, x % 5, -x);
    println("bitwise:", x & 0xF, x | 0x100, x ^ 1, ~x, x << 2, x >> 1);
    println("compare:", x == 17, x != 17, x < 20, x >= 18);

    let wide: i200 = -1;
    let top: u128 = 340282366920938463463374607431768211455;
    let size: usize = 4096;
    println("wide:", wide, top, size);

    let third = 1.0 / 3.0;          // `f64` unless used as an `f32`
    println("floats:", third, 0.1 + 0.2, 1e16, 2.5e-3);
}
Output
constants: 128 1267650600228229401496703205376 16777215 0.5
widened: 200 200 0.5
arithmetic: 20 -3 34 3 2 -17
bitwise: 1 273 16 -18 68 8
compare: true false true false
wide: -1 340282366920938463463374607431768211455 4096
floats: 0.3333333333333333 0.30000000000000004 1e+16 0.0025

Casts

value as T turns any number or bool into any number type:

  • An integer cast keeps the low bits.
  • A float cast to an integer drops the fraction and stops at the type's smallest or largest value. NaN gives 0.

A narrowing without as is an error, and the error offers the cast as a fix:

peeper check
[T0001]: mismatched types: expected `u8`, found `i32`  --> src/main.peep:3:21  | 2 |     let big: i32 = 300;3 |     let small: u8 = big;  |                     ^^^  |   = help: add `as u8` to convert, which can change the value  | 3 |     let small: u8 = big as u8;Compilation failed with 1 error(s)
examples/03_casts.peep
// Casts: `value as T` turns any number or `bool` into any number type. An
// integer cast keeps the low bits; a float cast to an integer drops the
// fraction and saturates at the type's ends, and NaN gives 0.
// Run: peeper run examples/03_casts.peep
fn main() {
    let big: i32 = 300;
    let negative: i32 = -1;
    println("integers:", big as u8, negative as u32, negative as i64);

    let half: f64 = -2.5;
    let huge: f64 = 1e20;
    let zero = 0.0;
    println("floats to integers:", half as i32, half as u16, huge as i32, (zero / zero) as i32);

    let count: i64 = 7;
    println("integers to floats:", count as f64 / 2.0, 0.1 as f32);

    println("bools:", true as u8, false as i32);
}
Output
integers: 44 4294967295 -1
floats to integers: -2 0 2147483647 0
integers to floats: 3.5 0.1
bools: 1 0

On this page