Troubleshooting
No Implicit Type Coercion
rill never converts between types automatically. Operations that silently coerce in other languages produce errors in rill.
String + Number
"count: " + 5
# Error: Arithmetic requires number, got stringFix: Use string interpolation or explicit conversion.
"count: {5}"
# Result: "count: 5"5 -> :>string
# Result: "5"Number from String
"42" -> $ + 1
# Error: Arithmetic requires number, got stringFix: Convert with :>number.
"42" -> :>number -> ($ + 1)
# Result: 43Non-numeric strings throw on conversion:
"abc" -> :>number
# Error: Cannot convert "abc" to numberNo Truthiness
rill requires actual bool values for conditions. Empty strings, zero, and empty lists are not “falsy.”
Condition Expects Boolean
"hello" ? "yes" ! "no"
# Error: Conditional requires boolean, got stringFix: Produce a boolean explicitly.
"hello" -> .empty -> (!$) ? "yes" ! "no"
# Result: "yes"0 -> ($ == 0) ? "zero" ! "nonzero"
# Result: "zero"Negation Requires Boolean
!"hello"
# Error: Negation requires boolean, got stringFix: Negate a boolean expression.
"hello" -> .empty -> !$
# Result: falseType-Locked Variables
Variables lock to the type of their first assignment. Reassigning a different type fails.
"hello" => $x
"world" => $x # OK: string to string42 => $x
# Error: cannot assign number to string variable $xFix: Use a new variable or convert the value.
"hello" => $x
42 -> :>string => $x # OK: still a stringMissing Dict Keys
Accessing a key that does not exist throws an error. rill has no undefined or null.
[name: "alice"] => $person
$person.age
# Error: Key 'age' not found in dictFix: Use ?? for a default value, or .?key to check existence.
[name: "alice"] => $person
$person.age ?? 0
# Result: 0[name: "alice"] => $person
$person.?age ? "has age" ! "no age"
# Result: "no age"List Index Out of Bounds
["a", "b"] => $list
$list[5]
# Error: List index out of boundsFix: Check length before accessing.
["a", "b"] => $list
$list -> .len -> .gt(5) ? $list[5] ! "default"
# Result: "default"Pipe Value ($) Outside Pipe
$ refers to the current pipe value. Using it outside a pipe context produces an error.
Fix: Capture with => when you need the value later.
"hello" => $greeting -> .upper
$greeting
# Result: "hello"Empty Collection Operations
Methods like .head and .tail error on empty collections.
[] -> .head
# Error: Cannot get head of empty listFix: Check .empty first.
[] => $list
$list -> .empty ? "nothing" ! $list -> .head
# Result: "nothing"Spread Type Mismatch
List spread requires a list operand. Dict spread requires a dict operand.
"hello" => $str
[...$str]
# Error: Spread in list literal requires list, got stringFix: Ensure the spread operand matches the container type.
"hello" -> .split("") => $chars
[...$chars]
# Result: ["h", "e", "l", "l", "o"]Closure Parameter Count
Calling a closure with wrong argument count produces an error.
|a, b|($a + $b) => $add
$add(1)
# Error: Expected 2 arguments, got 1Fix: Pass the correct number of arguments, or use default parameters.
|a, b = 0|($a + $b) => $add
$add(1)
# Result: 1Reserved Dict Keys
keys, values, and entries are reserved method names on dicts. Using them as keys produces errors.
[keys: "test"]
# Error: Reserved key 'keys'Fix: Choose a different key name.
[key_list: "test"] => $d
$d.key_list
# Result: "test"Debugging Tips
Use log for Pipeline Inspection
log prints its input and passes the value through unchanged, so it works inline.
"hello" -> log -> .upper -> log -> .len
# Logs: "hello"
# Logs: "HELLO"
# Result: 5Use json to Inspect Structure
[name: "alice", scores: [90, 85, 92]] -> json -> log
# Logs: {"name":"alice","scores":[90,85,92]}Use ^type to Check Types
[1, 2, 3] => $val
$val.^type.name -> log
# Logs: "list"
$val.^type.signature -> log
# Logs: "list(number)"Use Type Assertions to Validate
Insert :type assertions at pipe boundaries to catch unexpected types early.
[1, 2, 3] -> :list(number) -> map { $ * 2 }
# Result: [2, 4, 6]Stream Pitfalls
Re-Iterating a Consumed Stream
A stream can be iterated only once. Passing it to a second collection operator halts execution.
app::llm_stream("hello") => $s
$s -> each { $ }
$s -> map { $ }
# Error: RILL-R002: Stream already consumed; cannot re-iterateFix: Consume the stream once and store results in a variable if you need the data again.
app::llm_stream("hello") => $s
$s -> fold("") { $@ ++ $ } => $full_text
$full_text -> log
$full_text -> .len -> logyield Outside a Stream Closure
yield is a keyword scoped to stream closure bodies. Using it outside that context is a parse error.
"hello" -> yield
# Error: RILL-P: yield is not valid outside a stream closure bodyyield is also invalid inside a stored closure defined within a stream body.
|| {
{ $ -> yield } => $fn
$fn(1)
}:stream(number):number
# Error: yield is not valid in stored closureFix: Use yield only as a terminator in a pipe chain inside the stream closure body directly.
|| {
"first" -> yield
"second" -> yield
return 2
}:stream(string):number => $producerCalling $s() Before Consuming the Stream
Calling $s() on a stream that has not been fully iterated triggers internal consumption. All chunks are consumed before the resolution value is returned. This prevents separate chunk processing afterward.
app::llm_stream("hello") => $s
$s() # forces internal consumption of all chunks
$s -> each { $ -> log }
# Error: RILL-R002: Stream already consumed; cannot re-iterateFix: Iterate chunks first, then call $s() for the resolution value.
app::llm_stream("hello") => $s
$s -> each { $ -> log }
$s() # safe: stream is closed, resolution is cachedStale Step Access with .next()
Manual stream iteration with .next() creates new step objects. Holding a reference to an old step and calling .next() on it halts execution.
app::llm_stream("hello") => $s
$s.next() => $step1
$step1.next() => $step2
$step1.next()
# Error: RILL-R002: Stale step; this step is no longer currentFix: Always reassign the step variable when advancing. Use each for automatic iteration instead.
app::llm_stream("hello") => $s
$s -> each { $ -> log }$s() remains valid on stale steps. Only .next() fails when called on a non-current step.
See Also
| Document | Description |
|---|---|
| Error Reference | All error codes with causes and resolutions |
| Types | Type rules and value semantics |
| Design Principles | Why rill works this way |
| Guide | Beginner-friendly introduction |