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#
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 |
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#
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:
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 .{ ... }.
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.

