Quick Start
This guide will help you get started with rustico
quickly. We'll cover the basics of using the Result
type and show you how it compares to traditional error handling.
The Problem with Traditional Error Handling
Traditional error handling in Python using try/except blocks can be verbose, error-prone, and difficult to compose:
def process_user_data(user_id: str, age_str: str):
try:
user_id_int = int(user_id)
except ValueError:
return None # Lost error information!
try:
age = int(age_str)
except ValueError:
return None # Which field failed?
try:
if age < 0:
raise ValueError("Age cannot be negative")
return {"user_id": user_id_int, "age": age}
except ValueError:
return None # Nested try/except hell!
# Usage - You have to remember to check for None!
result = process_user_data("123", "25")
if result is None: # But WHY did it fail?
print("Something went wrong...")
The rustico
Way
With rustico
, error handling becomes explicit, composable, and clear:
from rustico import as_result, do, Ok, Err
@as_result(ValueError)
def parse_int(s: str) -> int:
return int(s)
@do
def process_user_data(user_id: str, age_str: str):
# Each step is explicit and composable
user_id_int = yield parse_int(user_id)
age = yield parse_int(age_str)
if age < 0:
return Err("Age cannot be negative")
return {"user_id": user_id_int, "age": age}
# Usage - Errors are explicit and informative
result = process_user_data("123", "25")
match result:
case Ok(data):
print(f"Success: {data}")
case Err(error):
print(f"Failed because: {error}")
Basic Usage Examples
1. Wrapping Functions
from rustico import as_result
@as_result(ValueError)
def parse_int(s: str) -> int:
return int(s)
print(parse_int("123")) # Ok(123)
print(parse_int("oops")) # Err(ValueError(...))
2. Handling Results
result = parse_int("456")
if result.is_ok():
value = result.unwrap()
print("Got:", value)
else:
error = result.unwrap_err()
print("Failed:", error)
3. Chaining Operations
from rustico import Ok, Err
def double(x: int):
return Ok(x * 2)
result = parse_int("21").and_then(double)
print(result) # Ok(42)
Composing Multiple Steps (do-notation)
from rustico import as_result, do
@do
def example():
# This yields Result[int, ValueError]
x = yield Ok(10) # x receives the unwrapped int (type T)
y = yield Ok(20) # y receives the unwrapped int (type T)
return x + y
print(example()) # Ok(30)
Early Exit on Error
@do
def safe_division(a: str, b: str):
x = yield parse_int(a)
y = yield parse_int(b)
if y == 0:
return Err("Division by zero")
return x / y
print(safe_division("100", "0")) # Err('Division by zero')
print(safe_division("100", "5")) # Ok(20.0)
print(safe_division("foo", "5")) # Err(ValueError(...))
Async Example
import asyncio
from rustico import as_async_result, do_async
@as_async_result(ValueError)
async def parse_int_async(s: str) -> int:
await asyncio.sleep(0.1)
return int(s)
@do_async
async def compute_async():
a = yield await parse_int_async("100")
b = yield await parse_int_async("23")
return a + b
async def main():
result = await compute_async()
if result.is_ok():
print("Async sum:", result.unwrap())
else:
print("Async error:", result.unwrap_err())
asyncio.run(main()) # Async sum: 123
Mapping and Error Transformation
result = parse_int("not a number").map(lambda x: x * 2).map_err(str)
print(result) # Err('invalid literal for int() with base 10: ...')
For more detailed examples and advanced usage, check out the Examples section.