Understanding Incan Error Messages¶
Incan’s compiler errors are designed to be actionable: they show you what went wrong, where it happened, and what to do next.
Reading Error Output¶
type error: Type mismatch: expected 'Result[str, str]', found 'str'
--> examples/demo.incn:8:5
|
8 | return "Alice"
| ^^^^^^^^^^^^^^
= note: In Incan, functions that can fail return Result[T, E]
= hint: Wrap the value with Ok(...) to return success
= hint: Or use Err(...) to return an error
Components:
- Error type:
type error,syntax error,warning,lint - Message: What went wrong
- Location: File, line, and column
- Source: The problematic code with underline
- Notes: Background explanation (why this matters)
- Hints: How to fix it
Common Errors and Solutions¶
Result Type Mismatches¶
Error:
Type mismatch: expected 'Result[User, Error]', found 'User'
Problem: Function returns Result but you're returning a plain value.
Solution:
# Wrong
def get_user(id: int) -> Result[User, Error]:
return User(name="Alice") # ❌ Missing Ok()
# Right
def get_user(id: int) -> Result[User, Error]:
return Ok(User(name="Alice")) # ✅
Error:
Type mismatch: expected 'str', found 'Result[str, Error]'
Problem: Using a Result where a plain value is expected.
Solution:
# Wrong
def greet() -> str:
name = get_name() # Returns Result[str, Error]
return name # ❌ name is still Result
# Right - use ? to unwrap
def greet() -> Result[str, Error]:
name = get_name()? # ✅ Unwraps Ok, returns Err early
return Ok(f"Hello, {name}")
# Or handle explicitly
def greet_safe() -> str:
match get_name():
case Ok(name): return f"Hello, {name}"
case Err(_): return "Hello, stranger"
The ? Operator¶
Error:
Cannot use '?' on type 'int' - expected Result[T, E]
Problem: The ? operator only works on Result types.
Solution:
# Wrong
def double(x: int) -> int:
return x? # ❌ x is not a Result
# Right
def double(x: int) -> int:
return x * 2 # ✅ No ? needed
Mutability Errors¶
Error:
Cannot mutate 'count' - variable is immutable
Problem: Trying to change a variable declared without mut.
Why: Incan variables are immutable by default for safety and clarity.
Solution:
# Wrong
def counter() -> int:
let count = 0
count = count + 1 # ❌ count is immutable
return count
# Right
def counter() -> int:
mut count = 0 # ✅ Declare as mutable
count = count + 1
return count
Self Mutation¶
Error:
Method 'increment' assigns to 'self.value' but takes 'self'
Problem: Method modifies self but doesn't declare mut self. The same report (INCAN-T0102) covers a call that changes a field, such as self.items.append(x) or self.items.pop(), and a call to one of the type's own mut self methods.
Solution:
model Counter:
value: int
# Wrong
def increment(self) -> None:
self.value += 1 # ❌ self is immutable
# Right
def increment(mut self) -> None: # ✅ mut self
self.value += 1
Missing Traits¶
Error:
Type 'Point' does not implement trait 'Eq'
Problem: Using == on a type without equality support.
Solution:
# Add @derive(Eq) to enable ==
@derive(Eq)
model Point:
x: int
y: int
# Now this works
if p1 == p2:
println("Same point!")
Common derives:
| Need | Derive |
|---|---|
==, != |
@derive(Eq) |
<, >, <=, >= |
@derive(Ord) |
Use in Set or as Dict key |
@derive(Hash, Eq) |
.clone() method |
@derive(Clone) |
{:?} debug format |
@derive(Debug) |
See Derives & Traits for more information on Derives and Traits
Option Handling¶
Error:
Type mismatch: expected 'bool', found 'Option[User]'
Problem: Using Option directly in a condition.
Solution:
# Wrong
let user = find_user(id)
if user: # ❌ Can't use Option as bool
...
# Right - use pattern matching control flow
if let Some(u) = user: # ✅
println(u.name)
# Or check explicitly
if user.is_some():
let u = user.unwrap()
println(u.name)
When you need to keep iterating while a pattern keeps matching, use while let instead of a while true plus match / break ladder:
mut current = find_user(id)
while let Some(user) = current:
println(user.name)
current = next_user(user)
Displaying a Value With No Printed Form¶
Error:
'println' cannot print the Point value 'point'
Problem: print, println, str and an f-string {value} share one display rule (see Display). A union value, a generator, a function, bytes, a value of type None, and a model, class, enum or newtype whose type provides no Display have no printed form in any of them (INCAN-T0103). A type provides Display through __str__, through the values an enum declares, through @derive(Display) (which shows the value's {value:?} structure), or through the message() of an Error adopter. The same refusal applies to a type argument for a Display bound. Lists, tuples, dicts, sets, Option and Result do print: println(items) prints [1, 2, 3].
Solution: Give the value a printed form, or display something that has one.
model Point:
x: int
y: int
# Right - define __str__ to give the model a printed form
def __str__(self) -> str:
return f"({self.x}, {self.y})"
def show(value: int | str) -> None:
# Right - narrow a union and print the member
match value:
int(n) => println(n)
str(s) => println(s)
# Right - declare values so each variant displays as its value
enum Level(str):
WARN = "warn"
def main() -> None:
point = Point(x=1, y=2)
println(point) # ✅ (1, 2)
println(f"{point:?}") # ✅ the structure: Point { x: 1, y: 2 }
println(Level.WARN) # ✅ warn
evens = (n * 2 for n in [1, 2, 3])
println(list(evens)) # ✅ collect a generator first: [2, 4, 6]
Collection Annotation Without Type Arguments¶
Error:
List annotation 'List' is missing its element type
Problem: list, dict, set, tuple, Option and Result (and the frozen collections and Generator) each name a family of types, so the bare word names no type (INCAN-T0104). The refusal also applies to a bare word inside another annotation, such as Option[List].
Solution:
# Wrong
items: List = [1, 2] # ❌
# Right
items: list[int] = [1, 2] # ✅
counts: dict[str, int] = {"a": 1}
maybe: Option[int] = None
Reserved __incan_ Names¶
Error:
The function '__incan_original_target' starts with '__incan_', a prefix reserved for names the compiler generates
Problem: A name the program declares or binds starts with __incan_, which is reserved for the compiler (INCAN-T0111; see Reserved name prefix).
Solution: Rename it without the prefix.
# Wrong
pub def __incan_original_target() -> int: # ❌
return 2
# Right
pub def original_target() -> int: # ✅
return 2
Philosophy: Explicit is Better¶
Incan intentionally requires explicit handling of:
- Errors - No silent failures; use
Resultand? - Nullability - No
Nonesurprises; useOption - Mutation - Immutable by default; explicit
mut
This catches bugs at compile time rather than runtime.
Getting Help¶
If an error message is unclear:
- Read the hints - they often show the exact fix
- Check the notes - they explain the underlying concept
- Look at Error Handling for
Result/Optionpatterns - See Derives & Traits for trait requirements
Next Steps¶
- Error Handling - Working with
Resulttypes - Derives & Traits - Drop trait for custom cleanup
- File I/O - Reading, writing, and path handling
- Async Programming - Async/await with Tokio
- Imports & Modules - Module system, imports, and built-in functions
- Rust Interop - Using Rust crates directly from Incan
- Web framework guide - Building web apps with Axum