The most common issues new Geno users encounter, with explanations and fixes.
1. Named Arguments Required for 3+ Parameters
Functions with three or more parameters require named arguments at call sites.
// Error: expected named arguments
let result: String = replace("hello world", "world", "geno")
// Fix: use parameter names
let result: String = replace("hello world", old: "world", new: "geno")
Why: Named arguments prevent argument-order bugs in functions with many parameters. Functions with 1-2 parameters can be called positionally.
2. Capability Denied
Error [E412]: Capability denied: 'print' requires the 'print' capability
Your program is calling a builtin that requires a capability you haven't
granted. In geno run, pair --cap with --unsafe to explicitly choose the
direct interpreter capability mode:
# Fix: grant the capabilities you need
geno run myfile.geno --unsafe --cap print,fs,http
See Capability Reference for the full table.
3. Immutable Binding Reassignment
// Error: cannot assign to immutable binding 'x'
let x: Int = 5
x = 10
// Fix: use var for mutable bindings
var x: Int = 5
x = 10
let creates an immutable binding. Use var when you need to reassign.
Prefer let by default — mutability should be intentional.
4. Missing Example Clauses
// Error: function 'add' must have at least one example clause
func add(a: Int, b: Int) -> Int
return a + b
end func
// Fix: add example clauses
func add(a: Int, b: Int) -> Int
example 1, 2 -> 3
example 0, 0 -> 0
return a + b
end func
Every function must have at least one example clause. Exemptions:
main, app lifecycle hooks (init, update, render), async functions,
functions called from test blocks, and functions annotated with @untested.
Examples serve as both documentation and lightweight tests — they are
verified at runtime. For impure functions that cannot have deterministic
examples, use the @untested annotation.
5. Boolean Literals Are Lowercase
// Error: undefined variable 'True'
let flag: Bool = True
// Fix: use lowercase
let flag: Bool = true
Geno uses true and false, not Python-style True/False.
6. No Null — Use Option[T]
Geno has no null. Use Option[T] with Some(value) and None to represent
optional values.
func find_user(id: Int) -> Option[String]
example 1 -> Some("Alice")
example 999 -> None
if id == 1 then
return Some("Alice")
end if
return None
end func
Use the ? operator to unwrap and propagate None. The ? operator
unwraps Some(x) to x, or returns None from the enclosing function:
func greet_user(id: Int) -> Option[String]
example 1 -> Some("Hello, Alice")
example 999 -> None
let name: String = find_user(id)? // String, not Option[String]
return Some("Hello, " + name)
end func
Built-in parsers follow the same convention: parse_int returns
Option[Int] and parse_float returns Option[Float], not Result. Match
on Some/None rather than Ok/Err:
func parse_or_zero(text: String) -> Int
example "42" -> 42
example "abc" -> 0
match parse_int(text) with
| Some(n) -> return n
| None -> return 0
end match
end func
7. Non-Exhaustive Pattern Matching
// Error: non-exhaustive match — missing variant 'None'
match maybe_value with
| Some(x) -> return x
end match
// Fix: handle all variants
match maybe_value with
| Some(x) -> return x
| None -> return 0
end match
match expressions must cover all variants of the type being matched. Use
a wildcard _ pattern if you want to ignore some cases:
match value with
| Some(x) -> return x
| _ -> return 0
end match
8. String Concatenation Uses +
// Error: undefined function '++'
let greeting: String = "Hello, " ++ name
// Fix: use + (same operator as arithmetic)
let greeting: String = "Hello, " + name
You can also use f-strings: f"Hello, {name}".
9. Integer Division Truncates Toward Zero
let x: Int = 7 / 2 // x = 3 (not 3.5)
let y: Int = -7 / 2 // y = -3 (truncates toward zero)
This matches C and JavaScript semantics. For float division, convert first:
let x: Float = int_to_float(7) / int_to_float(2) // x = 3.5
10. Block Delimiters
Every block must be closed with an end keyword matching the block type:
func greet(name: String) -> String
example "World" -> "Hello, World!"
if length(name) == 0 then
return "Hello, stranger!"
end if // not end, not }
return "Hello, " + name + "!"
end func // not end, not }
Block delimiters: end func, end if, end for, end while, end match,
end try, end trait, end impl.
The if keyword requires then after the condition. The for and while
keywords require do after the condition.
if x > 0 then // 'then' required
// ...
end if
for i in range(10) do // 'do' required
// ...
end for
11. Redeclaring a var Instead of Updating It
// Warning [W300]: 'var y' declares a new variable that hides the mutable 'y'
var y: Int = 110
for i: Int in range(0, 3) do
var y: Int = y + 40 // a second y, gone at end for
end for
// y is still 110 here
// Fix: assign to the existing var
var y: Int = 110
for i: Int in range(0, 3) do
y = y + 40
end for
Inside a nested block, let and var declare a separate variable. The new
y hides the outer one until the block ends, and the outer y keeps its old
value. In the same block, redeclaration instead updates the existing binding;
closures created before the redeclaration observe its new value. Redeclaring
with let also makes the binding immutable.
geno check reports W300 when a let or var redeclares a name whose nearest
binding is a var, in the same block or an enclosing one. The warning describes
which case applies. It also checks lambdas in default parameter values. The
program still passes; use assignment to update a var without redeclaring it,
or rename the variable if you meant to have two.
Rendered from docs/reference/common-pitfalls.md in the Geno repository.