Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Values, types and functions

Constants, functions and checks

# Constants, functions and checks.
const SECONDS_PER_MINUTE: i64 = 60

fn to_seconds(minutes: i64) -> i64 = minutes * SECONDS_PER_MINUTE

fn area(width: i64, height: i64) -> i64
    let a = width * height
    return a

check to_seconds(3) == 180
check area(4, 5) == 20
  • const names a value that never changes. Its type is written after :.
  • fn to_seconds(minutes: i64) -> i64 = ... is a function with one parameter of type i64 (a 64-bit signed integer) that returns an i64. When the body is a single expression, write it after =.
  • A longer body goes in an indented block. let names a value inside the block. return gives the result.
  • check is a test. The checker evaluates it while checking the program, and the program is accepted only if every check is true.

Names at the top level of a file can be used before they are declared, so the order of declarations does not matter.

Integer types

RCE has the integer types i8, i16, i32, i64 (signed), u8, u16, u32, u64 (unsigned) and usize (used for lengths and positions). Each one stands for exactly the whole numbers in its range: a u8 is a number from 0 to 255.

# Every integer type has a fixed range. Mixing types needs an explicit `as`.
fn small_sum(a: u8, b: u8) -> u8 = a + b

fn widen(a: u8, b: i64) -> i64 = (a as i64) + b

check small_sum(100, 27) == 127
check widen(200, 1000) == 1200
check (250 as u8).wrapping_add(10) == 4
check (250 as u8).saturating_add(10) == 255
check (250 as u8).checked_add(10).isnone()

A number written in the program is an i64 unless the place it is used needs a different integer type. In small_sum(100, 27) the parameters are u8, so 100 and 27 are u8.

Different integer types never mix silently. To add a u8 to an i64, turn it into an i64 with as first, as widen does.

When a result does not fit

What is 200 + 100 as a u8? The true answer, 300, is not a u8. Many languages quietly wrap it around to 44. RCE does not guess. The addition traps: it stops with an error. Because the check below needs that addition, the program is refused:

fn small_sum(a: u8, b: u8) -> u8 = a + b

check small_sum(200, 100) == 44

rce check says:

$ rce check overflow.rce
REFUSED overflow.rce: line 3: W-INT-1 check: trap: overflow: 300 is outside u8
exit status: 1

W-INT-1 is the name of the rule in the specification: every check and example must hold. When you want wrapping or clamping, ask for it by name, as sizes.rce does: wrapping_add wraps around, saturating_add stops at the largest value, and checked_add gives an option that is empty when the result does not fit. (Options come back in chapter 7.)

Mixing types without as is refused too, by the type rule W-TY-1:

fn add(a: u8, b: i64) -> i64 = a + b
$ rce check mixed_types.rce
REFUSED mixed_types.rce: line 1: W-TY-1 `+` on u8 and i64: no implicit conversion; line 1: W-TY-1 expected i64, found u8
exit status: 1

Other basic types

  • bool: true and false, combined with and, or, not.
  • f64 and f32: floating-point numbers. A literal such as 2.5 is an f64. The specification fixes how every operation rounds, so the same float code gives the same bits on every target.
  • text: a string of Unicode text, such as "hello". Chapter 8 covers it.
  • (): the unit type, the result of a function that returns nothing.

What you learned

  • Functions, constants and check tests.
  • Integer types have exact ranges, and going outside a range traps.
  • Conversions between types are always written with as.