Docs/Language basics

Variables & types

Declare variables, pick a number type, and convert values.

On this page

Valk checks types at compile time. Local variables infer their type from their value; function parameters and results are written out.

Declare a variable#

Valk
fn main() {
    let quantity = 3          // int
    let price: float = 4.5    // explicit type
    quantity += 2
    println("Total: %{quantity * price}") // Total: 22.5
}

A variable can be reassigned, but its type stays fixed. Unused variables give a warning.

Number types#

Type Values
int, uint Signed / unsigned, 64-bit on 64-bit targets
i8, i16, i32, i64 Signed, fixed width
u8, u16, u32, u64 Unsigned, fixed width
float 64-bit on 64-bit targets
f32, f64 Fixed width floats
bool true or false

Literals: 1_000_000, hex 0xff, octal 0c17, binary 0b1010, floats 2.5 and 2.5e-3. A character such as 'a' or '\n' is a u8. T.$min and T.$max give a type's range, such as u8.$max for 255.

Operators#

Operators Meaning
+ - * / % Arithmetic; / on integers rounds toward zero
== != < > <= >= Comparison
&& || ! Logic on bool
& | ^ ~ << >> Bits
+= -= *= /= ++ -- Update a variable
cond ? a : b Pick a value
Valk
fn main() {
    println(7 / 2)     // 3
    println(7 % 2)     // 1
    println(7.0 / 2)   // 3.5
    println(1 << 4)    // 16
    let count = 3
    println(count > 1 ? "items" : "item") // items
}

Integer arithmetic stays in the operands' type and wraps on overflow: for a u8 holding 250, n + 10 is 4. Convert to a wider type first when the result can be larger.

Convert between types#

Valk
fn main() {
    let small: u8 = 250
    let wide = small.to(uint) + 10
    println(wide)                    // 260
    println(10.to(float) / 4)        // 2.5
    println(3.9.to(int))             // 3
    println(3.14159.to_string(2))    // 3.14
    let text: String = 42            // numbers convert to String
    println(text)
}

.to(Type) converts between number types; a float to an integer drops the fraction, and a narrower integer keeps the low bits. A float prints as the shortest text that reads back as the same number; to_string(n) gives exactly n decimals.

Parse text#

Parsing can fail, so it is handled like any other error:

Valk
fn main() {
    let count = "42".to(int) !? 0
    let ratio = "2.5".to(float) !? 0.0
    let byte = "300".to(u8) !? 0    // out of range: 0
    let hex = "ff".hex_to_int() !? 0
    println("%count %ratio %byte %hex") // 42 2.5 0 255
}

"text".to(T) works for every number type. hex_to_int, hex_to_uint, octal_to_int and octal_to_uint read other bases.

Let the type fill in a name#

When the expected type is known, .name refers to something of that type without repeating it: an enum member, a static function, or a literal with .{ ... }.

Valk
enum Level { low, high }

fn describe(level: Level) String {
    return level == .high ? "loud" : "quiet"
}

fn main() {
    let scores: Array[int] = .{ 10, 20, 30 }
    let level: Level = .high
    println(describe(.low))     // quiet
    println(describe(level))    // loud
    println(scores == .{ 10, 20, 30 }) // true
}

This works for variables with a type, function arguments, class properties, return values and the right side of ==. A literal number takes the type of the value next to it: in x * 2, the 2 has the type of x.

Null values#

A type starting with ?, like ?String, may also hold null. See Null values.