irys 0.2.1

Compile-time trait reflection for Rust
Documentation
# irys

**If it can be expressed as a trait bound, you can reflect over it.**

Compile-time trait reflection for Rust. Automatically discover which traits a type implements — without per-type annotation, without proc macros, on stable Rust.

[![crates.io](https://img.shields.io/crates/v/irys.svg)](https://crates.io/crates/irys)
[![docs.rs](https://docs.rs/irys/badge.svg)](https://docs.rs/irys)
[![license](https://img.shields.io/crates/l/irys.svg)](LICENSE)

## Quick Start

```rust
use irys::*;
use std::fmt;

// 1. Define a capability
struct DisplayCap;
impl Capability for DisplayCap {
    type Handle = dyn fmt::Display;
}

// 2. Register it — ONE time, covers ALL types that implement Display
register_capability! {
    slot = 0,
    cap = DisplayCap,
    trait_bound = fmt::Display,
}

// 3. Reflect any value
let envelope = reflect!("hello world");
assert!(envelope.has::<DisplayCap>());

let display = envelope.get::<DisplayCap>().unwrap();
assert_eq!(format!("{}", display), "hello world");
```

## Why irys?

| System | Registration cost | Trait discovery? | Generic capabilities? |
|--------|-------------------|-----------------|----------------------|
| **irys** | **1 per trait** | Yes, automatic | Yes (with type params) |
| `bevy_reflect` | 1 per type + 1 per type×trait | Partial | No |
| `typetag` | 1 per type×trait | No (serde only) | No |
| `inventory`/`linkme` | 1 per type | No | No |
| `std::any::Any` | None | No (downcast only) | No |

With irys, you register a capability **once** and it blanket-covers every type satisfying the trait bound. 50 types implementing `Display`? One registration. 500 types? Still one registration.

## Features

- **Zero per-type boilerplate** — blanket detection via trait bounds
- **Generic capabilities**`Iterator<Item=T>`, `Future<Output=T>`, `Stream<Item=T>` with full type inference
- **Stable Rust** — no nightly, no proc macros, one dependency (`seq-macro`)
- **Registries** — namespace isolation, no slot conflicts between libraries
- **Pay for what you use** — probe only the slots you care about
- **Vec/slice ownership model**`Envelope`, `EnvelopeRef<'a>`, `EnvelopeMut<'a>`
- **Override semantics** — last-write-wins gives you specialization without specialization
- **Graceful mismatches** — mismatched capability maps return `None`, never panic
- **Order-independent fields**`register_capability!` fields in any order
- **Full compiler support** — Rust's type inference resolves capabilities, catches ambiguities

## Generic Capabilities

The real power of irys: register a capability **once** with a generic type parameter, and the compiler resolves it for every concrete type:

```rust
use std::marker::PhantomData;

struct StreamCap<I>(PhantomData<I>);
impl<I: 'static> Capability for StreamCap<I> {
    type Handle = dyn Stream<Item = I> + Unpin;
}

// Register ONCE — works for ALL item types
register_capability! {
    slot = 0,
    cap = StreamCap<I>,
    trait_bound = Stream<Item = I> + Unpin,
    generics = [I: 'static],
}

// Query with specific types:
envelope.has::<StreamCap<String>>()    // does it stream strings?
envelope.has::<StreamCap<Event>>()     // does it stream events?
envelope.get_mut::<StreamCap<u8>>()    // get a &mut dyn Stream<Item = u8>
```

This works with any trait that has associated types or type parameters:

```rust
// Futures
struct FutureCap<O>(PhantomData<O>);
impl<O: 'static> Capability for FutureCap<O> {
    type Handle = dyn Future<Output = O> + Unpin;
}

register_capability! {
    slot = 1,
    cap = FutureCap<O>,
    trait_bound = Future<Output = O> + Unpin,
    generics = [O: 'static],
}

// Iterators
struct IterCap<I>(PhantomData<I>);
impl<I: 'static> Capability for IterCap<I> {
    type Handle = dyn Iterator<Item = I>;
}

register_capability! {
    slot = 2,
    cap = IterCap<I>,
    trait_bound = Iterator<Item = I>,
    generics = [I: 'static],
}
```

**No other Rust reflection library can do this.** `bevy_reflect`, `typetag`, `inventory` — none support generic capability detection with full compiler inference. With irys, the Rust compiler does the heavy lifting: it infers type parameters, catches ambiguities at compile time, and eliminates unmatched probes entirely.

### The `register_capability!` Macro

Fields can be provided in **any order**. All fields:

| Field | Required | Description |
|-------|----------|-------------|
| `slot` | Yes | Slot number within the registry |
| `cap` | Yes | The capability marker type |
| `trait_bound` | Yes | Trait bound(s) that types must satisfy |
| `registry` | No | Registry (defaults to `DefaultRegistry`) |
| `generics` | No | Extra generic params: `[I: 'static, U: Clone]` |
| `where` | No | Additional where clause bounds: `[<I as Trait>::Assoc: Debug]` |

```rust
// All of these are equivalent:
register_capability! { slot = 0, cap = DebugCap, trait_bound = fmt::Debug }
register_capability! { cap = DebugCap, slot = 0, trait_bound = fmt::Debug }
register_capability! { trait_bound = fmt::Debug, cap = DebugCap, slot = 0 }
```

## Adapter Traits — Compositional Capabilities

The most powerful pattern: define an adapter trait that composes multiple constraints, register it once, and it's automatically detected on any type satisfying the combination.

"I don't care WHAT this iterates — just that each item is serializable":

```rust
// Adapter trait — erases the item type
trait SerializableIter {
    fn next_ser(&mut self) -> Option<Box<dyn erased_serde::Serialize>>;
}

// Blanket impl — any Iterator with Serialize items qualifies
impl<T: Iterator> SerializableIter for T
where T::Item: erased_serde::Serialize + 'static {
    fn next_ser(&mut self) -> Option<Box<dyn erased_serde::Serialize>> {
        self.next().map(|item| Box::new(item) as _)
    }
}

struct SerializableIterCap;
impl Capability for SerializableIterCap {
    type Handle = dyn SerializableIter;
}

register_capability! { slot = 0, cap = SerializableIterCap, trait_bound = SerializableIter }
```

Now ANY iterator with serializable items is detected — `Vec<LogEntry>`, `Vec<Metric>`, anything. No per-type registration. No enumerating item types. The compiler's trait resolution handles it.

**No other Rust reflection library can do this.** It requires blanket detection + full compiler inference, which only irys provides.

## Ownership Model

Like `Vec<T>` / `&[T]` / `&mut [T]`:

```rust
// Owned — consumes the value
let envelope = reflect!(value);

// Shared borrow — non-consuming, read-only
let envelope_ref = reflect_ref!(&value);
// value is still usable here

// Mutable borrow — non-consuming, mutable trait access
let mut envelope_mut = reflect_mut!(&mut value);
envelope_mut.get_mut::<ResettableCap>().unwrap().reset();
// value is mutated in place
```

Conversions:
- `envelope.as_ref()``EnvelopeRef`
- `envelope.as_mut()``EnvelopeMut`
- `envelope_mut.as_ref()``EnvelopeRef` (downgrade)

## Registries

Namespaces that isolate your capability slots from other libraries:

```rust
struct MyRegistry;

register_capability! {
    registry = MyRegistry,
    slot = 0,
    cap = SerializeCap,
    trait_bound = erased_serde::Serialize + Send + Sync,
}

// Only probe what you care about
let envelope = reflect!(value, [
    { registry: MyRegistry, slots: 0..5 },
]);
```

Two libraries can both use slot 0 without conflict — they're in different registries. You control compile-time cost by probing only the ranges you need.

## The `Reflectable` Trait

For generic code, implement `Reflectable` to enable reflection without knowing the concrete type:

```rust
struct MyEvent { data: String }

// One-liner via helper macro:
impl_reflectable!(MyEvent);

// Or with custom registries:
impl_reflectable!(MyEvent, [
    { registry: CoreRegistry, slots: 0..5 },
    { registry: ObsRegistry, slots: 0..3 },
]);

// Now generic code works:
fn publish(event: impl Reflectable) {
    let envelope = event.reflect();
    // ...
}
```

## Cloning Envelopes

Register `Clone` as a capability, then use `caps()` + `from_raw()`:

```rust
use std::any::Any;

trait DynClone: Send + Sync {
    fn clone_boxed(&self) -> Box<dyn Any + Send + Sync>;
}

impl<T: Clone + Send + Sync + 'static> DynClone for T {
    fn clone_boxed(&self) -> Box<dyn Any + Send + Sync> {
        Box::new(self.clone())
    }
}

struct CloneCap;
impl Capability for CloneCap {
    type Handle = dyn DynClone;
}

register_capability! {
    slot = 5,
    cap = CloneCap,
    trait_bound = DynClone,
}

// Clone an envelope:
let cloned_data = envelope.get::<CloneCap>().unwrap().clone_boxed();
let cloned_envelope = Envelope::from_raw(cloned_data, envelope.caps().clone());
```

The map clone is cheap (just `Arc` refcount bumps). Mismatched data/maps safely return `None`.

## Composing with Other Libraries

irys wraps any ecosystem trait as a capability:

```rust,ignore
// bevy_reflect — structural introspection
register_capability! { slot = 0, cap = ReflectCap, trait_bound = bevy_reflect::Reflect }

// erased_serde — type-erased serialization  
register_capability! { slot = 1, cap = SerializeCap, trait_bound = erased_serde::Serialize + Send + Sync }

// std::error::Error — error handling
register_capability! { slot = 2, cap = ErrorCap, trait_bound = std::error::Error + Send + Sync }

// Any dyn-safe trait in the ecosystem — just register it
```

## How It Works

irys uses **autoref specialization** + **const generics** on stable Rust:

1. `register_capability!` generates two impls per capability:
   - An inherent impl on `Probe<__ProbeTarget, Registry, N>` with a trait bound (fires when `__ProbeTarget: Trait`)
   - A blanket trait impl on `&Probe<__ProbeTarget, Registry, N>` (fallback no-op)

2. `reflect!` expands into a loop (via `seq!`) probing each slot. Rust's method resolution
   prefers the inherent impl when the bound is satisfied, otherwise the no-op trait fires.

3. The compiler resolves this **statically** — no runtime branching. Undetected capabilities
   are eliminated entirely by the optimizer.

4. Registries are type parameters on `Probe`, giving each namespace independent method resolution.

5. Generic capabilities (like `IterCap<I>`) work because the compiler monomorphizes at the `reflect!()` call site — it knows the concrete type, infers `I`, and resolves the correct `HasCap` impl.

## Limitations

- **Concrete types at `reflect!()` call site** — the compiler must see the actual type. For generic contexts, use `Reflectable`. This is the same limitation every Rust reflection library has.
- **Capabilities only propagate downward**`reflect!()` can only detect capabilities whose `register_capability!` was visible when the crate was compiled. Workaround: accept a pre-constructed `Envelope` or `impl Reflectable` to push the `reflect!()` call downstream.
- **Ambiguous generic registrations** — if a type implements `Trait<A>` AND `Trait<B>`, a generic registration for `Trait<T>` will fail with a compile error (the compiler can't pick which `T`). Register concrete instances instead (`Cap<A>` at slot 0, `Cap<B>` at slot 1). This is the compiler protecting you from an ambiguous capability map.
- **Manual slot numbers** — you pick them, the compiler catches collisions. Registries prevent cross-library conflicts.
- **`unsafe` internally** — fat pointer transport uses `transmute_copy`. Sound (guaranteed layout), but the internal code has `unsafe` blocks. The public API is fully safe.

## Installation

```toml
[dependencies]
irys = "0.2"
```

## License

MIT