Skip to main content

Rust-inspired implementations of Result<T, E>, Option<T> and Iter<T> data types for Python.

Project description

rusty

License: MIT Python 3.12+

Rust-inspired Result<T, E>, Option<T>, and Iter<T> types for Python.

rusty is a single, dependency-free module that brings Rust's most useful data-modeling patterns to Python: explicit error contracts alongside Python's exception mechanism, optional values instead of ambiguous None checks, and lazy, chainable iterators instead of nested comprehensions.

Why rusty?

  • Explicit error contracts. A Result-returning API makes modeled failure paths visible in its signature. Callers explicitly choose whether to propagate, transform, recover from, or unwrap the result.
  • Composable by design. map, and_then, or_else, filter, and friends let you build pipelines without intermediate if/else chains.
  • Familiar to Rust developers. Method names and core workflows closely follow std::result::Result, std::option::Option, and Iterator, with Python-friendly adaptations.
  • Zero dependencies, one file. Drop rusty.py into any project running Python 3.12+.
  • Fully tested. The test suite covers all library features.

Installation

rusty has no runtime dependencies. Its PyPI distribution is named rustypie and can be installed with pip:

pip install rustypie

The installed module is imported as rusty.

Alternatively, since the entire library is a single file, you can simply copy rusty.py into your project.

Table of Contents

Result: Ok / Err

Result[T, E] is a Union[Ok[T], Err[E]] representing either a success value or an error value as a visible return-value contract.

from rusty import Err, Ok, Result


def divide(a: int, b: int) -> Result[float, str]:
    if b == 0:
        return Err("division by zero")
    return Ok(a / b)


result = divide(10, 2)
assert result.is_ok()
assert result.unwrap() == 5.0

doubled = divide(10, 2).map(lambda value: value * 2).unwrap_or(0.0)
assert doubled == 10.0

# Errors short-circuit the chain instead of raising:
failure = divide(10, 0).map(lambda value: value * 2).unwrap_or(-1.0)
assert failure == -1.0

Ok and Err support Python's structural pattern matching too:

match divide(10, 0):
    case Ok(value):
        print(f"got {value}")
    case Err(message):
        print(f"failed: {message}")

Key methods available on both Ok and Err:

Method Description
is_ok() / is_err() Check the variant.
unwrap() Extract the success value, or raise on Err.
expect(msg) Extract the success value, or raise RuntimeError(msg) on Err.
unwrap_or(default) / unwrap_or_else(fn) Extract the value or fall back.
map(fn) / map_err(fn) Transform the success or error value.
map_or(default, fn) / map_or_else(default_fn, fn) Transform with a fallback.
and_then(fn) / or_else(fn) Chain fallible operations.
op_and(other) / op_or(other) Rust's and/or combinators.
ok() / err() Convert to Option[T] / Option[E].
as_ok() / as_err() Get self or None without unwrapping.
inspect(fn) / inspect_err(fn) Peek at the value for side effects.
iter() Get an Iter yielding zero or one item.

unwrap() returns the value from Ok. On Err, it re-raises the wrapped value when that value is an Exception; otherwise it raises a RuntimeError describing the error. expect(msg) also returns the value from Ok, but always raises RuntimeError(msg) on Err. These methods are intentional escape hatches for callers that explicitly choose exception-based handling at a boundary. Exceptions raised independently by user callbacks still propagate normally.

AsyncResult

AsyncResult[T, E] wraps an Awaitable[Result[T, E]], letting you chain map, and_then, map_err, and or_else — synchronously or with async callbacks — before finally await-ing the resolved Result. Once the awaitable resolves to a Result, that value is cached and returned by later awaits.

from rusty import Ok, Result, async_result


async def fetch_value() -> Result[int, str]:
    return Ok(21)


async def transformed_value() -> Result[int, str]:
    return await (
        async_result(fetch_value())
        .map(lambda value: value * 2)
        .and_then(lambda value: Ok(value + 1))
    )

map, map_err, and_then, and or_else accept plain callables or ones returning awaitables, so sync and async steps can be mixed freely in the same chain. AsyncResult also exposes async ok(), err(), as_ok(), as_err(), unwrap_or_else(), and iter() for resolving into Option/Iter values.

Option

Option[T] represents an optional value (Some(value) or None) without Python's ambiguity around whether a bare None means "absent" or "an actual None value".

from rusty import Option


name = Option.some("Ferris").map(str.upper).unwrap_or("UNKNOWN")
assert name == "FERRIS"

missing = Option.none().map(str.upper).unwrap_or("UNKNOWN")
assert missing == "UNKNOWN"

Notable methods:

Method Description
is_some() / is_none() Check the variant.
unwrap() Extract the value, or raise ValueError on None.
expect(msg) Extract the value, or raise ValueError(msg) on None.
unwrap_or(default) / unwrap_or_else(fn) Extract with a fallback.
map(fn), map_or(default, fn), map_or_else(default_fn, fn) Transform the value.
and_then(fn) / or_else(fn) Chain optional-returning operations.
op_and(other) / op_or(other) / op_xor(other) Combine two Option values.
filter(predicate) Keep the value only if it matches a predicate.
zip(other) / zip_with(other, fn) Combine two Option values.
ok_or(err) Convert to Result[T, E].
take() Move the value out, leaving None behind.
iter() Get an Iter yielding zero or one item.

Option.some(value) raises ValueError if value is None — use Option.none() (or the Option(value) constructor) to represent absence explicitly. For an absent value, unwrap() raises a standard descriptive ValueError, while expect(msg) raises ValueError with the supplied message.

Iter

Iter[T] wraps any Iterable[T] with lazy, chainable combinators inspired by Rust's Iterator trait. Nothing is evaluated until a terminal method (like collect(), fold(), or for_each()) consumes the iterator.

from rusty import into_iter


values = (
    into_iter([1, 2, 3, 4, 5, 6])
    .filter(lambda value: value % 2 == 0)
    .map(lambda value: value * 10)
    .collect()
)
assert values == [20, 40, 60]

Transformations return a new lazy Iter:

Method Description
map(fn) / filter(predicate) / filter_map(fn) Transform or prune elements.
flat_map(fn) / flatten() Flatten one level of Iterable, Result, or Option.
take(n) / take_while(predicate) Limit the front of the iterator.
skip(n) / skip_while(predicate) Drop from the front of the iterator.
chain(other) / zip(other) Combine with another iterable.
enumerate() Pair each item with its index.
windows(size) Yield overlapping tuples of size (like slice::windows).
inspect(fn) Run a side effect as each item is consumed, yielding it unchanged.

Terminal methods consume the iterator and return a concrete result:

Method Description
collect() / collect_set() / collect_tuple() Materialize into a list, set, or tuple.
fold(init, fn) / reduce(fn) Accumulate into a single value.
all(predicate) / any(predicate) Boolean tests over all elements.
find(predicate) / position(predicate) / rposition(predicate) Locate a matching element.
count() / last() / nth(n) / next() Positional access.
partition(predicate) Split into (matched, unmatched) lists.
for_each(fn) Run a side effect per item, returning None.

flatten() and flat_map() understand Ok, Err, and Option in addition to plain iterables, so you can flatten a stream of Result/Option values directly:

from rusty import Err, Iter, Ok


assert Iter([Ok(1), Err("skip me"), Ok(3)]).flatten().collect() == [1, 3]

Collecting Result Values

collect_ok, collect_ok_set, and collect_ok_tuple turn an Iter[Result[T, E]] (or any iterable of Result-like objects) into a single Result. They short-circuit and return the first Err encountered, or an Ok containing all successfully unwrapped values:

from rusty import Err, Iter, Ok, collect_ok, collect_ok_set, collect_ok_tuple


values = [Ok(1), Ok(2), Ok(3)]

assert collect_ok(Iter(values)) == Ok([1, 2, 3])
assert collect_ok_set(Iter(values)) == Ok({1, 2, 3})
assert collect_ok_tuple(Iter(values)) == Ok((1, 2, 3))

assert collect_ok(Iter([Ok(1), Err("invalid"), Ok(3)])) == Err("invalid")

Iteration stops as soon as an Err is found, so any remaining items are never evaluated.

Decorators

Wrap existing exception-raising or None-returning functions (or methods) without rewriting their bodies:

Decorator Use for Wraps into
@resultify Functions that may raise an exception. Result[T, Exception]
@resultify_method Instance methods that may raise an exception. Result[T, Exception]
@optionable Functions that may return None. Option[T]
@optionable_method Instance methods that may return None. Option[T]
from typing import Optional

from rusty import optionable, resultify


@resultify
def parse_int(value: str) -> int:
    return int(value)


assert parse_int("42").map(lambda n: n * 2).unwrap() == 84
assert parse_int("nope").is_err()


@optionable
def find_user(user_id: int) -> Optional[str]:
    return {1: "Ferris"}.get(user_id)


assert find_user(1).unwrap_or("unknown") == "Ferris"
assert find_user(2).unwrap_or("unknown") == "unknown"

Quick Reference

from rusty import (
    AsyncResult,
    Err,
    Iter,
    Ok,
    Option,
    Result,
    async_result,
    collect_ok,
    collect_ok_set,
    collect_ok_tuple,
    into_iter,
    optionable,
    optionable_method,
    resultify,
    resultify_method,
)

Testing

The project uses pytest. Install the development dependencies and run the test suite from the repository root:

python -m pip install -e ".[dev]"
python -m pytest

License

MIT License. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rustypie-0.1.1.tar.gz (21.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rustypie-0.1.1-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file rustypie-0.1.1.tar.gz.

File metadata

  • Download URL: rustypie-0.1.1.tar.gz
  • Upload date:
  • Size: 21.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rustypie-0.1.1.tar.gz
Algorithm Hash digest
SHA256 93a75c0df179706103e9646436faf09f3c37bfdd9ce95e04eab9ca93c7f2772f
MD5 cb2e7a10889488b32a8287ef7ce13456
BLAKE2b-256 9afc82e52d586ce0238da653bb6a46333ffa64b05506284e5b563e0b9d10bc64

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustypie-0.1.1.tar.gz:

Publisher: release.yml on dshein-alt/rusty

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustypie-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: rustypie-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 11.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rustypie-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9b9eeff3894da6e7337f079a32b12f65b69c622fa5b30d599fd7995dbd1e288c
MD5 a1187b8a898fbc2f22bf3583254f0ff0
BLAKE2b-256 f3a35e68de18534a2098b65e93244baccdc4356406c9689b94c4b18948f5a2c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustypie-0.1.1-py3-none-any.whl:

Publisher: release.yml on dshein-alt/rusty

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page