> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# June 10, 2026 — Binary: inline attribute storage via SmallVec

> Attrs now stores up to 2 attributes inline, cutting per-node heap allocations on the encode path. IqError::Disconnected now wraps Box<Node>.

## Performance

**Binary: `Attrs` backed by `SmallVec` for inline storage ([#819](https://github.com/oxidezap/whatsapp-rust/pull/819))**

`Attrs` previously used a plain `Vec` as its backing store, so every node on the encode path paid one heap allocation just for the attribute buffer. In a steady-state send/receive profile this was \~7 allocations per message stanza. On a group fanout it was two per recipient (`to` + `enc`).

`Attrs` now uses `AttrsVec` — `SmallVec<[(Cow<'static, str>, NodeValue); 2]>` — so nodes carrying ≤2 attributes keep them on the stack alongside the node with zero heap allocation. Nodes with ≥3 attributes spill to the heap as before.

**Measured impact (iai-callgrind, instruction counts):**

| Benchmark                                          | Delta                                                    |
| -------------------------------------------------- | -------------------------------------------------------- |
| `marshal_allocating` (typical stanza)              | −6.6% instructions                                       |
| `marshal_reusing_buffer`                           | −6.5% instructions                                       |
| `marshal_many_children_allocating` (2048 children) | −9.9% instructions, −10% estimated cycles, −25% RAM hits |
| `marshal_long_string`                              | +4.4% (+208 instructions, content-dominated shape)       |
| unmarshal / unpack / attr\_parser                  | unchanged                                                |

Allocation counts per iteration (counting allocator): message-shaped stanza 15→11 (−27%); group stanza with 800 participants 4012→2412 (−40%), bytes allocated −22%.

## Breaking changes

**`Attrs.0` type (`wacore-binary`)**

`Attrs.0` changes from `Vec<(Cow<'static, str>, NodeValue)>` to `AttrsVec`. Construction via `Attrs::new()`, `Attrs::with_capacity()`, `NodeBuilder`, iteration, and the serde representation are all unchanged (the serde wire format is byte-identical). Code that held a reference to `Attrs.0` typed as `Vec` needs updating:

```rust theme={null}
// Before
let v: &Vec<_> = &attrs.0;

// After — use AttrsVec directly, or convert:
let v: Vec<_> = attrs.0.into_vec();
```

`AttrsVec` is re-exported as `wacore_binary::node::AttrsVec`. The `spilled()` method on `SmallVec` reports whether the list has overflowed to the heap.

**`IqError::Disconnected` now carries `Box<Node>`**

Both `wacore::request::IqError` and the main crate's mirror change `Disconnected(Node)` to `Disconnected(Box<Node>)`. The larger `Node` size after the `SmallVec` inline array triggered clippy's `large_enum_variant`; boxing the rare disconnect payload is correct regardless.

```rust theme={null}
// Before
IqError::Disconnected(node) => { /* node: Node */ }

// After
IqError::Disconnected(boxed) => { let node = *boxed; }
```
