tinyklv 0.1.0

The simplest Key-Length-Value (KLV) framework in Rust
Documentation
# Sigil coercion and `EncodeAs`

An `enc` path (`#[klv(enc = ...)]` on a field, or `enc = ...` inside a
container `default(...)` / `key(...)` / `len(...)` block) may carry a
leading sigil - `&` or `*` - that tells the macro how to shape the call
to the underlying encoder function. This page explains the three forms
and when to use each.

## Why sigils exist

The [`EncodeValue`](./traits.md) trait is defined as:

```rust,ignore
pub trait EncodeValue<O: EncodedOutput> {
    fn encode_value(&self) -> O;
}
```

`&self` gives the derive macro access to each field by reference. So
when the macro emits the per-field encode call, it has `&field_value`
in hand.

But encoders come in three shapes:

1. **Borrowed-form encoders** already take a reference:

   ```rust,ignore
   fn my_encoder(input: &InnerValue) -> Vec<u8> { ... }
   ```

   `&field_value` matches `&InnerValue` directly.

2. **`EncodeAs`-coerced encoders** take a cheaply-borrowed "natural"
   view of the value:

   ```rust,ignore
   pub fn utf8(s: &str) -> Vec<u8> { ... }   // String -> &str
   pub fn slice(xs: &[u8]) -> Vec<u8> { ... } // Vec<u8> -> &[u8]
   ```

3. **Value-form encoders** - how all the built-in primitive encoders in
   [`tinyklv::codecs::binary::enc`]./codecs.md are written - take the
   value by value:

   ```rust,ignore
   pub fn u8(x: u8) -> Vec<u8> { ... }
   pub fn be_u16(x: u16) -> Vec<u8> { ... }
   ```

   Passing `&u8` to `fn u8(x: u8)` is a type error. We need a bridge.

The `&` and `*` sigils are those bridges.

## The three forms

| Syntax | Encoder signature | Emitted call |
|---|---|---|
| `enc = path` | `fn(&T) -> O` | `path(&self.field)` |
| `enc = &path` | `fn(T::Borrowed) -> O` | `path(EncodeAs::encode_as(&self.field))` |
| `enc = *path` | `fn(T) -> O` | `path(self.field)` |

### No sigil - direct borrow

Use when your encoder already takes `&T`. Every encoder generated by
`#[derive(Klv)]` falls in this bucket, because `EncodeValue::encode_value`
takes `&self`:

```rust,ignore
#[klv(
    key = 0x04,
    dec = GpsFix::decode_value,
    enc = GpsFix::encode_value,  // &GpsFix -> Vec<u8>, no sigil needed
)]
gps: GpsFix,
```

### With `&` sigil - `EncodeAs` bridge

Use when your encoder takes a borrowed view that differs from `&T`
(primitives by value, `String -> &str`, `Vec<T> -> &[T]`, smart pointers
to inner):

```rust,ignore
#[klv(key = 0x02, dec = decb::be_u16, enc = &encb::be_u16)]
temperature_centideg: u16,
```

Under the hood (simplified):

```rust,ignore
encb::be_u16(EncodeAs::encode_as(&self.temperature_centideg))
```

For primitives `EncodeAs::encode_as(&u16)` is `*self` - a `Copy` - so
this compiles down to a plain call with zero overhead.

### With `*` sigil - by-value for `Copy` types

Use when your encoder takes `T` by value and `T: Copy`. Emits
`path(self.field)` directly, without the `EncodeAs` hop:

```rust,ignore
#[klv(key = 0x01, dec = decb::u8, enc = *encb::u8)]
sequence: u8,
```

This is the shortest path for primitive `Copy` types paired with
value-taking encoders. Prefer `&` when in doubt - it works for
non-`Copy` owned types too.

## The `EncodeAs` trait

```rust,ignore
pub trait EncodeAs {
    type Borrowed<'a> where Self: 'a;
    fn encode_as(&self) -> Self::Borrowed<'_>;
}
```

Built-in implementations:

| Input type | `Borrowed<'a>` | Behaviour |
|---|---|---|
| `u8`..`u128`, `i8`..`i128`, `f32`, `f64`, `bool`, `char`, `usize`, `isize` | `&'a Self` | pass through |
| `String` | `&'a str` | borrow as `&str` |
| `Vec<T>` | `&'a [T]` | borrow as slice |
| `Cow<'_, str>` | `&'a str` | to `&str` |
| `Cow<'_, [T]>` | `&'a [T]` | to slice |
| `Box<T>`, `Rc<T>`, `Arc<T>` | `&'a T` | deref to inner |

Every conversion is zero-cost: no clones, no allocations.

## When to implement `EncodeAs` yourself

You don't need to - for custom structs, use the no-sigil form and point
`enc` at a function taking `&YourType`. The derive-generated
`YourType::encode_value` already has that signature.

Implement `EncodeAs` on your own type **only** when:

- You want your type to work with the `&` sigil form, AND
- You want the encoder to receive a borrowed view distinct from `&Self`
  (e.g. a newtype over `String` that should coerce to `&str`).

This is rare.

## Summary

- `enc = path` - encoder takes `&T`, field is borrowed directly.
- `enc = &path` - encoder takes the `EncodeAs::Borrowed` view of `T`.
- `enc = *path` - encoder takes `T` by value (requires `T: Copy`).
- Use the no-sigil form for custom types whose encoders take `&Self`
  (including all `#[derive(Klv)]`-generated `encode_value` methods).
- Use the `&` form for the built-in primitive encoders in
  `tinyklv::codecs::binary::enc` and for owned containers like `String`
  / `Vec<T>`.
- Use the `*` form as a shortcut when both sides are primitive `Copy`
  types.