Skip to content
Noodle
InstallLearnPlayground
GitHub

Values and bindings

Every expression has a type. This chapter focuses on how values receive names and how those names behave. Review Basic types first if the primitive types and their literal forms are not yet familiar.

The initializer of a local binding usually determines its type:

func main() -> Unit do
count = 3;
label = "items";
Debug.trace((count, label));
end

Add an annotation when it documents intent or supplies information the initializer does not determine:

func main() -> Unit do
answer : Int = 42;
Console.println(answer.to_string());
end

Annotations constrain the value; they do not convert it. The initializer must already have the annotated type.

An ordinary local binding cannot be assigned later:

func subtotal(price : Int, quantity : Int) -> Int do
result = price * quantity;
result
end

A later binding may reuse the same spelling, but it shadows the earlier name instead of changing its storage:

func normalized(value : Int) -> Int do
value = if value < 0 then 0 else value end;
value
end

The initializer of the second value sees the parameter. Only the new binding is visible afterward.

Use mut and := when one storage location must change across nested control flow:

func count_steps(limit : Int) -> Int do
mut count = 0;
for index = 0; index < limit; index = index + 1 do
count := count + 1;
end;
count
end

A mutable binding has one fixed type for its entire lifetime. Assignment is a statement, not an expression, and the right-hand side is evaluated before the slot is replaced.

Prefer immutable data and loop state when a value simply evolves from one iteration to the next. Use mutation when nested code must observe the same storage location.

Every binding must be used. Prefix a deliberately unused parameter or binding with _:

func keep_left(left : Int, _right : Int) -> Int do
left
end

Use _ = expression; to evaluate and intentionally discard a result without creating a name.

A constant is immutable, known during compilation, and requires an explicit type:

const MAX_RETRIES : Int = 3
const PRODUCT_NAME : String = "Noodle"
func main() -> Unit do
Console.println(PRODUCT_NAME);
Debug.trace(MAX_RETRIES);
end

Constant names use uppercase snake case. The current constant-expression subset accepts primitive literals, constant references, and constant arrays; it does not evaluate arbitrary function calls, operators, records, tuples, or datatype constructors during compilation.

Next: functions.