Skip to content

Statements

A code block is a sequence of statements enclosed in { ... }. Code blocks form the body of a function, the handler of a callback connection, and the block form of a binding:

clicked => {
let n = counter + 1;
counter = n;
}
slint

A binding may instead hold a single expression terminated by a semicolon; the two forms are interchangeable. Both forms are reactive: Slint tracks the properties read while evaluating them.

The value of a code block is the value of its last statement, unless a return statement exits earlier.

The statements below are the complete set the parser accepts inside a code block. Nothing else is a statement; anything that is not one of these forms is parsed as an expression statement, and unrecognized syntax is a parse error.

Any expression followed by a semicolon is a statement, evaluated for its effect and its value discarded (except as the last statement of a block):

clicked => {
debug("hello");
root.some-callback();
}
slint

This is the form used to call a callback or function.

= assigns a new value to a property. The self-assignment operators +=, -=, *=, and /= combine the corresponding operator with the assignment:

clicked => {
counter = 42;
counter += 1;
}
slint

The left-hand side must be a writable place: a property reference, a field of a property of struct type, an element of a property of array type, or the model value inside a for element. Assigning to anything else — a local variable, a function call, or any other expression — is a compile error.

The property must be writable from where the assignment appears. Assigning to a property whose visibility does not permit it is an error, and so is assigning to a property that is linked to a read-only property, or to a constexpr property that must be known at compile time.

The right-hand side must have the property’s type. += also applies to string and to length-like types; -=, *=, and /= apply only to length-like types; using any of them on another type is an error.

let declares a local variable, visible from its declaration to the end of the enclosing code block:

clicked => {
let greeting = "hello world"; // type inferred from the value
let count: int = 2; // explicit type annotation
debug(greeting, count);
}
slint

The type annotation is optional; without it the type is inferred from the initializer, which is required. A local variable is immutable: it cannot be assigned to after declaration, since assignment targets must be properties. A name that is already in scope — a local variable or a callback/function parameter — cannot be redeclared by a let, even in a nested block; doing so is a compile error.

:= is not an assignment operator in statements. Writing name := <expression>; is an error that suggests let name = <expression>; instead.

if, together with optional else if and else branches, runs a code block conditionally:

clicked => {
if condition {
foo = 42;
} else if other-condition {
bar = 28;
} else {
foo = 4;
}
}
slint

The condition is an expression; parentheses around it are optional. Each branch is a code block. An else branch is either another if or a final code block.

An if at the start of a statement is only taken as an if statement when it is not immediately followed by ., ,, ;, }, ], or ); in those cases if is read as an ordinary identifier within an expression.

There is no for or while statement. Repetition over a model is expressed with the for element in the element tree, not inside a code block.

return ends the enclosing function, callback handler, or code-block binding and yields its value:

clicked => {
if disabled {
return;
}
counter += 1;
}
slint

The returned expression is optional. If the surrounding function or callback declares a return type, return without a value is an error, and the value’s type must match the declared return type. Without any return, the block yields the value of its last statement.

A lone semicolon is an empty statement:

clicked => { ; }
slint

© 2026 SixtyFPS GmbH