6. Errors (Result/Option and ?)¶
Incan uses explicit error values (Result and Option) instead of Python-style exceptions.
Result[T, E]¶
Use Result for operations that can fail.
def read_username() -> Result[str, str]:
name = input("Name: ").strip()
if len(name) == 0:
return Err("name must not be empty")
return Ok(name)
Handle it with match:
def main() -> None:
match read_username():
case Ok(name): println(f"hello, {name}")
case Err(e): println(f"error: {e}")
Option[T]¶
Use Option for “value may be absent”:
Option[T] has two variants:
Some(value)— value is presentNone— value is absent
You create a present value by wrapping it: Some(x).
def first(items: List[str]) -> Option[str]:
if len(items) == 0:
return None
return Some(items[0])
You handle it with match by destructuring Some(...).
def main() -> None:
match first(["a", "b"]):
case Some(x): println(f"first={x}")
case None: println("empty")
Coming from Python?
In Python typing, you’d usually express “may be missing” as:
from typing import Optional
def first(items: list[str]) -> Optional[str]:
return items[0] if items else None
Optional[T] is mostly a type-hinting/tooling concept.
In Incan, Option[T] is an explicit enum that the compiler can reason about and enforce.
Propagating errors with ?¶
The ? operator returns early on Err, otherwise unwraps the Ok value:
def greet_user() -> Result[None, str]:
name = read_username()?
println(f"hello, {name}")
return Ok(None)
Structured errors (recommended)¶
Prefer structured errors over strings when the caller should branch on error kinds:
enum NameError:
Empty
def normalize(name: str) -> Result[str, NameError]:
cleaned = name.strip()
if len(cleaned) == 0:
return Err(NameError.Empty)
return Ok(cleaned.lower())
Try it¶
- Write
def safe_div(a: float, b: float) -> Result[float, str]. - Write
def first(items: List[str]) -> Option[str]and handleNonevsSome(...)withmatch. - Write
def parse_and_divide(n: float, s: str) -> Result[float, str]that uses?to parsesinto afloat, then callssafe_div.
One possible solution
def safe_div(a: float, b: float) -> Result[float, str]:
if b == 0.0:
return Err("division by zero")
return Ok(a / b)
def first(items: List[str]) -> Option[str]:
if len(items) == 0:
return None
return Some(items[0])
def parse_and_divide(n: float, s: str) -> Result[float, str]:
denom = float(s)?
return safe_div(n, denom)
def main() -> None:
match first(["a", "b"]):
case Some(x): println(f"first={x}")
case None: println("empty")
match parse_and_divide(10.0, "2.0"):
case Ok(x): println(f"10 / 2 = {x}")
case Err(e): println(f"error: {e}")
What to learn next¶
- Results, Options, and the
?operator (deep dive): Error Handling - Common error message patterns: Error Messages
Next¶
Back: 5. Modules and imports
Next chapter: 7. Strings and formatting