Bringing useful tools from Rust to Python.
rusty-utils
is available on PyPI.
Install using pip
:
pip install rusty-utils
Or with poetry
:
poetry add rusty-utils
rusty-utils
brings Rust-inspired constructs like Result
and Option
to Python, allowing developers to write cleaner
and more robust error-handling code.
For more details, check out the full documentation.
The Result
type is inspired by Rust, enabling you to handle success (Ok
) and failure (Err
) in a clean, expressive
way.
from rusty_utils import Result, Ok, Err
# Success case
success: Result[int, Exception] = Ok(42)
# Failure case
failure: Result[int, Exception] = Err(Exception("An error occurred"))
You can alias your own custom Result
types to suit your domain-specific needs.
from typing import TypeVar
from rusty_utils import Result, Ok, Err
class MyError(Exception): pass
_T = TypeVar("_T")
MyResult = Result[_T, MyError]
# Custom success and failure cases
success: MyResult[int] = Ok(42)
failure: MyResult[int] = Err(MyError("Something went wrong"))
In Rust, the ?
operator is used to easily propagate errors up the call stack, allowing you to return early if a
function fails, and to handle success (Ok
) and failure (Err
) values concisely.
fn side_effect() -> Resut<i32, Error> {
// some code that may fail
Ok(42)
}
fn process() -> Result<(), Error> {
let result = side_effect()?; // Propagates the error if it occurs
... // Other operation
}
In Python, you can't overload the ?
operator (Python even didn't treat ?
as a valid operator)
result = side_effect()? # What???
In Python, something has similar ability to throw the Err
and remain the Ok
value is:
Python built-in try-except
.
from rusty_utils import Catch
def side_effect() -> float:
# Simulate a potential failure (e.g., division by zero)
return 42 / 0
@Catch(Exception, ZeroDivisionError)
def wrapped_side_effect() -> float:
return side_effect()
@Catch(Exception)
def process() -> None:
result1 = wrapped_side_effect().unwrap_or_raise() # You achieve same functionality with `unwrap_or_raise()`!
result2 = Catch(Exception, ZeroDivisionError, func=side_effect).unwrap_or_raise()
In this example:
- We use the
@Catch(Exception)
decorator to make sure we can capture the raisedErr
and return to the caller.- What the
@Catch(E)
do is transform the function into a capturer which returnsResult[T, E]
- What the
- We can use the
Catch
in this way (result2) to capture the result of a fallible function call into aResult
. - The
wrapped_side_effect()
function returns aResult
that might be an error (Err
) or a valid value (Ok
).- Since the function returns a
float
and we mark it might raise anException
, so it actually returns aResult[float, Exception]
.
- Since the function returns a
- Then use
unwrap_or_raise()
to handle the result: if it's an error, it raises the exception, effectively mimicking Rust's?
operator.
This approach enables cleaner error propagation and handling in Python, much like in Rust, but using Python’s exception-handling style.
Although the
@Catch
decorator accpets multiple exception types, it's recommended to use it only for one type of exception at a time, or your linter might can't resolve the type hints correctly. (like it might think thewrapped_side_effect
returns aResult[float, Any]
)
-
Querying Result Type:
is_ok()
: ReturnsTrue
if theResult
isOk
.is_err()
: ReturnsTrue
if theResult
isErr
.
-
Unwrapping Values:
expect(message)
: Unwraps the value or raisesUnwrapError
with a custom message.unwrap()
: Unwraps the value or raisesUnwrapError
.unwrap_or(default)
: Returns the provided default value ifErr
.unwrap_or_else(func)
: Returns the result of the provided function ifErr
.unwrap_or_raise()
: Raises the exception contained inErr
.
-
Transforming Results:
ok()
: TransformsOk
toOption[T]
err()
: TransformsErr
toOption[E]
map(func)
: Appliesfunc
to theOk
value.map_err(func)
: Appliesfunc
to theErr
value.map_or(default, func)
: Appliesfunc
toOk
or returnsdefault
ifErr
.map_or_else(f_err, f_ok)
: Applies different functions depending on whether theResult
isOk
orErr
.
-
Logical Operations:
and_(other)
: Returns the secondResult
if the first isOk
; otherwise returns the originalErr
.or_(other)
: Returns the firstOk
, or the secondResult
if the first isErr
.and_then(func)
: Chains another operation based on theOk
value.or_else(func)
: Chains another operation based on theErr
value.
The Option
type expands Python's Optional
, representing a value that may or may not be present (Some
or None
).
from rusty_utils import Option
some_value: Option[int] = Option(42)
none_value: Option[int] = Option()
-
Querying Option Type:
is_some()
: ReturnsTrue
if theOption
contains a value.is_none()
: ReturnsTrue
if theOption
contains no value.
-
Unwrapping Values:
expect(message)
: Unwraps the value or raisesUnwrapError
with a custom message.unwrap()
: Unwraps the value or raisesUnwrapError
.unwrap_or(default)
: Returns the provided default value ifNone
.unwrap_or_else(func)
: Returns the result of a provided function ifNone
.
-
Transforming Options:
map(func)
: Transforms theSome
value.map_or(default, func)
: Transforms theSome
value or returns a default ifNone
.map_or_else(default_func, func)
: Transforms theSome
value or returns the result of a default function ifNone
.
-
Logical Operations:
and_(other)
: Returns the secondOption
if the first isSome
; otherwise returnsNone
.or_(other)
: Returns the firstSome
, or the secondOption
if the first isNone
.and_then(func)
: Chains another operation based on theSome
value.or_else(func)
: Chains another operation based on theNone
value.
Here are more practical examples of using Result
and Option
in real-world scenarios.
from rusty_utils import Result, Ok, Err
def fetch_data() -> Result[dict, Exception]:
try:
# Simulating an API call
data = {"id": 824, "name": "Kobe Bryant"}
return Ok(data)
except Exception as e:
return Err(e)
result = fetch_data()
if result.is_ok():
print("Success:", result.unwrap())
else:
print("Error:", result.unwrap_err())
from rusty_utils import Option
def get_value() -> Option[int]:
return Option(42)
some_value = get_value()
print(some_value.unwrap_or(0)) # Outputs: 42
For more advanced use cases, consult the full documentation.