Skip to content

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:

  1. Errors - No silent failures; use Result and ?
  2. Nullability - No None surprises; use Option
  3. Mutation - Immutable by default; explicit mut

This catches bugs at compile time rather than runtime.

Getting Help

If an error message is unclear:

  1. Read the hints - they often show the exact fix
  2. Check the notes - they explain the underlying concept
  3. Look at Error Handling for Result/Option patterns
  4. See Derives & Traits for trait requirements

Next Steps