Metadata-Version: 2.5
Name: pydorust
Version: 0.1.4
Summary: A Python library inspired by Rust
Project-URL: Homepage, https://git.ruject.fun/RuJect/pydorust
Project-URL: Repository, https://git.ruject.fun/RuJect/pydorust.git
Project-URL: Issues, https://git.ruject.fun/RuJect/pydorust/issues
Author-email: rus07tam <rus07tam+contact@ruject.fun>
License: Unlicense
License-File: LICENSE
Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: exceptiongroup>=1.3.1
Requires-Dist: pytest>=9.1.1
Requires-Dist: typing-extensions>=4.16.0
Description-Content-Type: text/markdown

# pydorust

A Python library bringing Rust programming language paradigms, semantics, and strict typing patterns to Python. Rather than attempting to enforce physical memory safety (which is fundamentally limited by Python's dynamic runtime), `pydorust` provides familiar Rust semantics, zero-cost abstractions, and structural patterns:

- **Macros (`pydorust.macros`)**: Procedural, declarative, and built-in AST metaprogramming.
- **Traits (`pydorust.trait`)**: True ad-hoc polymorphism and interface decoupling.
- **Algebraic Data Types (`pydorust.adt`)**: Tagged unions via `Enum` with nested `Variant` classes.
- **Option and Result (`pydorust.types`)**: Explicit error handling with `Option[T]` (`Some`, `Null`) and `Result[T, E]` (`Ok`, `Err`).
- **Interior Mutability (`pydorust.lock`)**: `Cell`, `RefCell` with runtime borrow checking, `OnceLock`, and `LazyLock`.
- **Synchronization Primitives (`pydorust.sync`)**: Semantic `Mutex` and `RwLock` with RAII-style context manager guards.
- **Fixed-Width Numbers (`pydorust.num`)**: `u8`..`u64`, `i8`..`i64`, `usize`, `isize` with wrapping, checked, saturating, and overflowing arithmetic.
- **Panics and Unsafe Contracts (`pydorust.panics`, `pydorust.unsafe`, `pydorust.mem`)**: Explicit `panic()`, `unreachable()`, `@unsafe_func`, and raw memory allocations.

## Installation

```bash
pip install pydorust
```

## Macros

The macro system brings syntactic metaprogramming to Python, inspired by Rust's procedural and declarative macros. Macros operate at the Python Abstract Syntax Tree (AST) level, allowing you to transform syntax, inline computations, and generate boilerplate at compile time or import time.

### Invocation Forms

`pydorust` supports three ways to invoke macros in code:

1. `m_call(macro, target, *args)`: Invokes a macro as an expression or statement. The first argument is the macro, and subsequent arguments are passed as AST nodes.
2. `@m_decorator(macro, *args)`: Wraps a macro for use as a standard Python decorator on functions or classes.
3. `m_derive(macro, *args)`: A statement-level macro that intercepts and transforms the next AST node immediately following the call.

### Built-in Macros

`pydorust.macros.builtins` provides standard macros:

#### Compile-Time Evaluation: `comptime`

Evaluates Python expressions at compile/expansion time and inlines the literal result into the AST:

```python
from pydorust.macros import m_call
from pydorust.macros.builtins import comptime

# Evaluated once during macro expansion:
BUFFER_SIZE = m_call(comptime, 1024 * 1024 * 4)
# Compiles to: BUFFER_SIZE = 4194304
```

#### Compile-Time Environment: `env` and `option_env`

Reads environment variables during macro expansion and inlines their string literals into the AST (identical to Rust's `env!()` / `option_env!()`):

```python
from pydorust.macros import m_call
from pydorust.macros.builtins import env

# Inlined as a string constant at compile time:
BUILD_PROFILE = m_call(env, "APP_ENV")
```

#### Compile-Time File Inclusion: `include_str` and `include_bytes`

Embeds the contents of an external file directly into the AST as a string or bytes literal at compile time (mirroring Rust's `include_str!()` / `include_bytes!()`):

```python
from pydorust.macros import m_call
from pydorust.macros.builtins import include_str

# Inlines file contents directly into the code:
SCHEMA_JSON = m_call(include_str, "schema.json")
```

### Declarative Macros: `@m_define_declarative`

Inspired by Rust's `macro_rules!`, declarative macros allow writing template-based code generators. Arguments represent AST variables, and names or strings prefixed with `__q_` are replaced with the input AST nodes:

```python
from typing import Any
from pydorust.macros import m_call, m_decorator
from pydorust.macros.builtins import m_define_declarative


@m_decorator(m_define_declarative)
def make_getter(field_name: Any) -> Any:
    def __q_field_name(self):
        return self._value


class Item:
    def __init__(self, value: int) -> None:
        self._value = value

    # Generates: def get_value(self): return self._value
    m_call(make_getter, get_value)


item = Item(42)
print(item.get_value())  # 42
```

Declarative macros also support substituting into classes, docstrings, and f-strings:

```python
@m_decorator(m_define_declarative)
def create_service(service_name: Any, endpoint: Any) -> Any:
    class __q_service_name:
        """Client for __q_service_name."""

        URL = __q_endpoint

        def connect(self) -> str:
            return f"Connected to {__q_service_name.URL}"


m_call(create_service, PaymentService, "https://api.payments.com")
```

### Imperative AST Macros: `@m_define`

For advanced transformations, `@m_define` allows writing procedural macros that inspect and manipulate AST nodes directly using `MacroContext`:

```python
import ast
from pydorust.macros import m_define, MacroContext, MacroResult


@m_define
def assert_positive(ctx: MacroContext, node: ast.AST, *args: ast.AST) -> MacroResult:
    code = ctx.unparse(node)
    return ctx.parse(
        f"if not ({code} > 0): raise ValueError(f'{code} must be positive, got {{{code}}}')",
        unwrap_module=True,
    )
```

### Tooling and Build Integration

Macros can be expanded in multiple workflows:

1. **CLI Tool (`python -m pydorust.macros`)**:
   ```bash
   # Expand a file and print the transformed Python code
   python -m pydorust.macros expand my_module.py

   # Compile a directory, expanding all macros into a target build folder
   python -m pydorust.macros compile src/ --out build/

   # Generate type stub (.pyi) files for macro-expanded code
   python -m pydorust.macros stubs src/ --out stubs/
   ```

2. **Hatchling Build Hook**:
   `pydorust` registers a `hatch` build hook (`pydorust.plugins.hatch`). When building wheels or sdist packages (`hatch build`), macros are automatically expanded ahead of packaging.

3. **Pytest Integration**:
   `pydorust` registers a pytest plugin (`pydorust.plugins.pytest`) via `pytest11`. Any test suite running under pytest automatically expands macros at import time without extra configuration.

4. **Runtime Expansion**:
   ```python
   from pydorust.macros import enable_runtime_expand, disable_runtime_expand

   enable_runtime_expand()  # Installs import hook in sys.meta_path
   import my_macro_code  # Expanded on import

   disable_runtime_expand()
   ```

## Traits

`pydorust` introduces Rust-style traits, decoupling method and property declarations from class inheritance. This enables ad-hoc polymorphism (extension methods) on existing classes — including built-in types like `int`, `str`, or third-party classes — without monkey-patching.

### Core Components

- `Trait`: Base class for defining a trait interface.
- `@trait_method`: Declares a required method that implementors must provide.
- `@default_method`: Provides a default implementation that can call other trait methods.
- `@trait_property`: Declares a required property.
- `@default_property`: Provides a default property implementation.
- `@impl(Trait, for_type=...)`: Registers a trait implementation for a specific type and verifies completeness at registration time.

### Example

```python
from pydorust.trait import (
    Trait,
    impl,
    trait_method,
    default_method,
    trait_property,
    default_property,
)


class Summary(Trait):
    @trait_method
    def summarize_author(self) -> str:
        """Required method: implementors must provide this."""

    @default_method
    def summarize(self) -> str:
        """Provided default method: calls summarize_author()."""
        return f"(Read more from {self.summarize_author()}...)"

    @trait_property
    def headline(self) -> str:
        """Required property."""

    @default_property
    def full_summary(self) -> str:
        """Provided default property combining headline and summarize."""
        return f"{self.headline} -- {self.summarize()}"


class Article:
    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author


@impl(Summary)
class ArticleSummary(Article):
    def summarize_author(self) -> str:
        return f"@{self.author}"

    @property
    def headline(self) -> str:
        return self.title


article = Article("Rust in Python", "rus07tam")

# Type checking works seamlessly:
assert isinstance(article, Summary)

# Dispatch style A: Trait proxy wrapper
summary_view = Summary(article)
print(summary_view.summarize_author())  # "@rus07tam"
print(summary_view.summarize())  # "(Read more from @rus07tam...)"
print(summary_view.full_summary)  # "Rust in Python -- (Read more from @rus07tam...)"

# Dispatch style B: Static / functional call
print(Summary.summarize(article))  # "(Read more from @rus07tam...)"
print(
    Summary.full_summary(article)
)  # "Rust in Python -- (Read more from @rus07tam...)"
```

## Algebraic Data Types

In Rust, algebraic data types are represented via `enum`, where each variant can optionally contain different types and amounts of data. In `pydorust`, this pattern is implemented through the `Enum` class containing nested `Variant` classes.

Each nested `Variant` class is automatically treated as an immutable (frozen) data structure:

```python
from pydorust.adt import Enum, Variant


class Message(Enum):
    # Unit variant (no data)
    class Quit(Variant):
        pass

    # Struct-like variant with fields
    class Move(Variant):
        x: int
        y: int

    class Write(Variant):
        text: str

    class ChangeColor(Variant):
        r: int
        g: int
        b: int
```

### Instantiation and Type Guarantees

Variants can be instantiated directly as members of the Enum:

```python
msg1 = Message.Quit
msg2 = Message.Move(x=10, y=20)
msg3 = Message.Write(text="Hello, Rust!")

# Type hierarchy is strictly verified:
assert isinstance(msg1, Message)
assert isinstance(msg2, Message)
assert isinstance(msg2, Message.Move)
assert issubclass(Message.Move, Message)
```

### Handling Variants

Variants can be inspected and branched on using standard `isinstance` checks:

```python
def process_message(msg: Message) -> str:
    if isinstance(msg, Message.Quit):
        return "Application quit."
    if isinstance(msg, Message.Move):
        return f"Moved to coordinates ({msg.x}, {msg.y})."
    if isinstance(msg, Message.Write):
        return f"Message content: {msg.text}"
    if isinstance(msg, Message.ChangeColor):
        return f"Color changed to RGB({msg.r}, {msg.g}, {msg.b})."
    raise TypeError(f"Unknown message variant: {msg}")


print(process_message(Message.Move(x=100, y=200)))
# "Moved to coordinates (100, 200)."
```

## Types: Option and Result

Rust eliminates null reference errors via `Option<T>` and manages fallible operations through `Result<T, E>`.

### `Option[T]` (`Some` and `Null`)

Represents an optional value: every `Option` is either `Some(value)` or `Null`:

```python
from pydorust.types import Option, Some, Null, ret_option


@ret_option
def find_first_even(numbers: list[int]) -> int | None:
    for n in numbers:
        if n % 2 == 0:
            return n
    return None


opt: Option[int] = find_first_even([1, 3, 5, 8, 9])
assert opt.is_some()
assert opt.unwrap() == 8

empty_opt: Option[int] = find_first_even([1, 3, 5])
assert empty_opt.is_null()
assert empty_opt.unwrap_or(0) == 0

# Functional combinators: .map(), .filter(), .and_then()
doubled = opt.map(lambda x: x * 2).unwrap_or(0)  # 16
```

### `Result[T, E]` (`Ok` and `Err`)

Represents either success (`Ok`) or failure (`Err`):

```python
from pydorust.types import Result, Ok, Err, ret_result


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


res_ok: Result[int, Exception] = parse_int("42")
assert res_ok.is_ok()
assert res_ok.unwrap() == 42

res_err: Result[int, Exception] = parse_int("invalid")
assert res_err.is_err()
assert res_err.unwrap_or(0) == 0

# Chaining operations safely:
computed = (
    parse_int("10")
    .map(lambda x: x * 5)
    .and_then(lambda x: Ok(f"Result: {x}"))
    .unwrap_or("Fallback")
)
print(computed)  # "Result: 50"
```

## Interior Mutability

The `pydorust.lock` module brings Rust-style interior mutability primitives to Python:

### `RefCell[T]`: Runtime Borrow Checking

`RefCell` provides dynamically checked borrow rules at runtime using context managers:
- Multiple immutable borrows (`with cell.borrow():`) are allowed simultaneously.
- Only one mutable borrow (`with cell.borrow_mut():`) is permitted at a time.
- Conflicting borrows raise a `RuntimeError`:

```python
from pydorust.lock import RefCell

data = RefCell([1, 2, 3])

# Multiple immutable borrows:
with data.borrow() as b1, data.borrow() as b2:
    print(b1.value, b2.value)

# Single mutable borrow:
with data.borrow_mut() as m:
    m.value.append(4)

# Conflicting borrows raise RuntimeError:
with data.borrow():
    with data.borrow_mut():  # Raises RuntimeError: "already borrowed"
        pass
```

### `Cell[T]`

A container providing interior mutability for copyable or value-like data without borrow-check tracking overhead:

```python
from pydorust.lock import Cell

cell = Cell(10)
print(cell.unsafe_get())  # 10
cell.unsafe_set(25)
print(cell.unsafe_get())  # 25
```

### `OnceLock[T]` and `LazyLock[T]`

Thread-safe primitives for once-initialization and lazy evaluation:

```python
from pydorust.lock import OnceLock, LazyLock

# OnceLock: Write once, read many times
config = OnceLock()
assert config.try_get().is_null()
config.get_or_init(lambda: {"env": "production", "port": 8080})
print(config.unwrap())  # {'env': 'production', 'port': 8080}

# LazyLock: Computed on first access
heavy_resource = LazyLock(lambda: sum(range(1_000_000)))
print(heavy_resource.get())  # Computed and cached on first call
```

## Synchronization Primitives

Provides RAII-style semantic synchronization primitives mirroring Rust's `std::sync`:

### `Mutex[T]` and `MutexGuard`

Protects shared data behind an exclusive lock:

```python
import threading
from pydorust.sync import Mutex

counter = Mutex(0)


def worker():
    for _ in range(1000):
        with counter.lock() as guard:
            guard.value += 1


threads = [threading.Thread(target=worker) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()

with counter.lock() as guard:
    print(guard.value)  # 5000
```

### `RwLock[T]`

Allows concurrent read access by multiple readers, or exclusive access by a single writer:

```python
from pydorust.sync import RwLock

data = RwLock({"key": "initial"})

# Readers
with data.read() as reader:
    print(reader.value["key"])

# Writer
with data.write() as writer:
    writer.value["key"] = "updated"
```

## Fixed-Width Numbers

`pydorust.num` introduces fixed-width numeric types matching Rust integer specifications:

- Unsigned: `u8`, `u16`, `u32`, `u64`, `usize`
- Signed: `i8`, `i16`, `i32`, `i64`, `isize`

Fixed-width types prevent silent overflows and provide explicit Rust arithmetic modes:

```python
from pydorust.num import u8, i32

# Bounds checking:
val = u8(255)
# u8(256) raises Panic: "out of bounds"

# 1. Wrapping arithmetic (modulo 2^N)
assert u8(255).wrapping_add(2) == 1
assert u8(0).wrapping_sub(1) == 255

# 2. Checked arithmetic (returns Option[T] to prevent panics)
assert u8(250).checked_add(5).unwrap() == 255
assert u8(250).checked_add(10).is_null()

# 3. Saturating arithmetic (clamps to MIN / MAX bounds)
assert u8(250).saturating_add(20) == 255
assert u8(10).saturating_sub(20) == 0

# 4. Overflowing arithmetic (returns tuple: (result, did_overflow))
res, overflow = u8(250).overflowing_add(10)
assert res == 4 and overflow is True
```

## Panics, Unsafe, and Memory Management

### Unrecoverable Errors (`pydorust.panics`)

For irrecoverable conditions, `pydorust` provides explicit panic primitives:

- `panic("reason")`: Terminates the program or raises `Panic` (depending on `panic_mode`).
- `unimplemented()`: Explicit crash for missing features.
- `unreachable()`: Marks unreachable code paths.
- `panic_mode(PanicMode.ABORT | PanicMode.RAISE)`: Configures whether panics terminate the process (`os._exit(1)`) or raise a Python `Panic` exception (useful for test runners).

```python
from pydorust.panics import panic, unimplemented, unreachable, panic_mode, PanicMode

panic_mode(PanicMode.RAISE)


def handle_choice(option: int):
    if option == 1:
        return "Option 1"
    elif option == 2:
        return "Option 2"
    else:
        unreachable()
```

### Unsafe Contracts (`pydorust.unsafe`)

In Rust, operations that circumvent safety invariants must reside inside `unsafe` blocks. `pydorust` establishes this explicit contract using `@unsafe_func` and `unsafe_call`:

```python
from pydorust.unsafe import unsafe_func, unsafe_call


@unsafe_func
def raw_hardware_access(address: int) -> None:
    print(f"Accessing memory at {address:#x}")


# raw_hardware_access(0x1000)  # Type error / linter warning
unsafe_call(raw_hardware_access, 0x1000)  # Explicit contract
```

### Low-Level Memory (`pydorust.mem`)

Exposes direct memory management primitives wrapping `ctypes.pythonapi`:

```python
from pydorust.mem import RawBuffer

with RawBuffer(128) as buf:
    buf.write(b"Hello from raw memory!")
    data = buf.read(22)
    print(data)  # b'Hello from raw memory!'
```

## License

This project is dedicated to the public domain under the [Unlicense](https://unlicense.org/). See [LICENSE](LICENSE) for details.
