Values and Types
Rue is statically typed: every value has a type the compiler knows before the program runs. This chapter covers the basic ones and the two habits you will use constantly: let bindings and building strings for output.
const std = @import("std");
fn main() -> i32 {
let answer: i32 = 42;
let big: i64 = 1000000000000;
let byte: u8 = 255;
let ready = true;
println("answer = " + @to_string(answer));
println("big = " + @to_string(big));
println("byte = " + @to_string(byte));
if ready {
println("ready");
}
0
}
answer = 42
big = 1000000000000
byte = 255
ready
Integers
Rue has signed integers i8, i16, i32, i64 and unsigned integers u8, u16, u32, u64. The number is the width in bits. Signed integers can be negative; unsigned ones cannot.
There are no implicit conversions between them, not even from a narrower type to a wider one. Mixing widths is a compile error, and you convert explicitly with @intCast:
const std = @import("std");
fn main() -> i32 {
let small: i32 = 1000;
let wide: i64 = @intCast(small);
println(@to_string(wide * 1000000));
0
}
1000000000
@intCast checks that the value fits. Converting 300 to a u8 is a runtime error, not a silent truncation to 44.
Arithmetic is checked too. If an operation overflows its type, the program stops with an error rather than wrapping around:
fn main() -> i32 {
let mut x: u8 = 250;
println("adding 10 to 250 as a u8");
x = x + 10;
println("unreachable");
0
}
adding 10 to 250 as a u8
The program prints the first line, then exits with status 101 and the message error: integer overflow on standard error. Rue calls this a trap. Chapter 11 has more to say about them.
Booleans
bool is true or false. Comparisons produce booleans, and if requires one. There is no truthiness: an integer is not a boolean.
fn main() -> i32 {
let n = 7;
let odd = n % 2 == 1;
if odd {
println("odd");
} else {
println("even");
}
0
}
odd
Type inference
You rarely need to write types. The compiler infers a binding's type from its initializer, and an integer literal with no other context becomes an i32:
const std = @import("std");
fn main() -> i32 {
let x = 42; // i32
let y = true; // bool
let z: u64 = 42; // the annotation picks u64 instead
println(@to_string(x) + " " + @to_string(z));
if y {
println("y");
}
0
}
42 42
y
Mutability
Bindings are immutable by default. let mut makes one assignable:
const std = @import("std");
fn main() -> i32 {
let mut count = 0;
count = count + 1;
count += 1; // compound assignment does the same thing
println("count = " + @to_string(count));
0
}
count = 2
Assigning to a plain let is a compile error:
fn main() -> i32 {
let x = 42;
x = 43;
x
}
error: [E0203]: cannot assign to immutable variable 'x'
Floats
f32 and f64 are IEEE-754 floating point. A literal with a decimal point is a float, and integers and floats do not mix without a conversion:
const std = @import("std");
fn main() -> i32 {
let half: f64 = 0.5;
println(@to_string(half * 3.0));
0
}
1.5
Strings and output
You have already used "..." literals with println. A string literal has the type str: a read-only view of some bytes. To build a string at runtime, for example to put a number in a message, you need the standard library's growable string type, StrBuf. That is what @to_string returns, and + joins strings into a new StrBuf:
const std = @import("std");
fn main() -> i32 {
let width = 3;
let height = 4;
let message = "area = " + @to_string(width * height);
println(message);
0
}
area = 12
This is why most programs in this tutorial start with const std = @import("std");. Rue has no prelude: nothing from the standard library is in scope until you import it, and @to_string produces a standard library type. Leave the import out and the compiler tells you exactly that:
fn main() -> i32 {
println("n = " + @to_string(42));
0
}
error: [E0204]: unknown type 'StrBuf'
There are no format strings in Rue. Concatenation with + and @to_string is the whole formatting story for now, and it is enough for everything in this tutorial. Booleans have no @to_string; branch on them, or use std.fmt.bool_to_string:
const std = @import("std");
fn main() -> i32 {
let done = false;
println("done = " + std.fmt.bool_to_string(done));
0
}
done = false
Chapter 10 comes back to strings in more depth. For now: literals are str, built strings are StrBuf, and println accepts either.