Skip to content

Phase 13.6 static channels#92

Draft
JustinKovacich wants to merge 6 commits into
feature/phase13_5_no_std_clientfrom
feature/phase13_6_static_channels
Draft

Phase 13.6 static channels#92
JustinKovacich wants to merge 6 commits into
feature/phase13_5_no_std_clientfrom
feature/phase13_6_static_channels

Conversation

@JustinKovacich
Copy link
Copy Markdown
Contributor

Introduce a static-pool ChannelFactory

JustinKovacich and others added 6 commits April 28, 2026 08:14
Prep work for phase 13.6 (static-pool ChannelFactory). Fixes a
trait-shape bug uncovered during 13.6 design: `ChannelFactory::bounded`
declares `<const N: usize>` but the associated-type GAT didn't carry
N, so backends that need N at the storage level (`embassy-sync`)
silently hardcoded a single capacity (16) regardless of the call-site
request. The const-generic on the method was advisory only; the
storage shape never honored it.

# Trait change (breaking)

Before:
    type BoundedSender<T: Send + 'static>: MpscSend<T>;
    type BoundedReceiver<T: Send + 'static>: MpscRecv<T>;
    fn bounded<T: Send + 'static, const N: usize>()
        -> (Self::BoundedSender<T>, Self::BoundedReceiver<T>);

After:
    type BoundedSender<T: Send + 'static, const N: usize>: MpscSend<T>;
    type BoundedReceiver<T: Send + 'static, const N: usize>: MpscRecv<T>;
    fn bounded<T: Send + 'static, const N: usize>()
        -> (Self::BoundedSender<T, N>, Self::BoundedReceiver<T, N>);

# Backend impls

- `tokio_transport::TokioChannels`: passes N through; ignored at the
  storage level (tokio mpsc stores capacity at runtime).
- `embassy_channels::EmbassySyncChannels`: now actually uses the
  call-site N for the embassy `Channel<_, T, N>` storage. Previously
  hardcoded to 16.

# Storage-site standardization

`SocketManager`'s bounded sender/receiver fields now spell out
`BoundedReceiver<_, 16>` / `BoundedSender<_, 16>` — both bind paths
(discovery + unicast) standardized to N=16. Unicast was historically
N=4, a tokio-conservative choice with no semantic requirement; bumping
to 16 matches what embassy already used and what discovery already
asked for.

`Inner`'s control channel stays at N=4 (it's a separate channel) —
its storage type is now `BoundedReceiver<_, 4>` / `BoundedSender<_, 4>`.

# Why this is its own commit

Phase 13.6's main work is the static-pool `ChannelFactory` impl
(`StaticChannels<NO, NB, NU>` with per-T monomorphization via a
`static_channels!` macro, atomic free-list reclamation, and close
semantics for graceful run-loop shutdown). The const-N fix is
genuinely independent of that work and benefits any future
ChannelFactory impl that cares about per-channel capacity. Landing
it separately keeps the 13.6-main commit focused on the static-pool
design.

# Verification

- `cargo test --all-features --lib`: 457 / 457 pass.
- `cargo clippy --all-features --all-targets`: clean.
- `tests/bare_metal_client` witness still passes.

# What this leaves for 13.6 (main)

The static-pool `ChannelFactory` itself: `StaticChannels<const NO,
const NB, const NU>` with per-T pool storage, atomic free-list,
poison-flag close semantics, and a `static_channels!` macro that
consumers invoke with their distinct payload types. Plus rewriting
the `bare_metal_client` witness to use StaticChannels (dropping the
`JoinHandle::abort()` workaround the EmbassySyncChannels impl forced).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reshape `ChannelFactory` so the three constructor methods
(`oneshot`, `bounded`, `unbounded`) gain `where T: *Pooled<Self>` bounds
and dispatch to that trait's pair-builder by default, instead of being
direct constructors. Three new traits in `transport.rs`:

  - `OneshotPooled<C>: Send + Sized + 'static`
  - `BoundedPooled<C, const N: usize>: Send + Sized + 'static`
  - `UnboundedPooled<C>: Send + Sized + 'static`

Each carries a `*_pair() -> (C::Sender<Self>, C::Receiver<Self>)`
constructor. `TokioChannels` and `EmbassySyncChannels` publish blanket
`impl<T: Send + 'static> *Pooled<Self> for T` (TokioChannels also
blankets bounded over `const N`), so existing user code is unaffected.

A static-pool `ChannelFactory` (phase 13.6c+) instead publishes
per-`T` `*Pooled<Self>` impls — typically generated by a macro — each
pointing at a declared `static` pool. Calling
`C::oneshot::<NotDeclared>()` against such a backend fails at the
call site with `OneshotPooled<MyChannels> is not implemented for
NotDeclared`, turning "forgot to declare a pool" from a runtime
panic into a compile error.

What this leaves for 13.6c:
- `src/static_channels/` module with pool primitives (slot, pool,
  free-list, send/recv handle types) and atomic ordering.
- The `static_channels!` macro (13.6d) that generates per-`T`
  `*Pooled<MyChannels>` impls from a user pool-layout declaration.
- The alloc-panicking witness test (13.6e).

Bound propagation:
- The 7-bound bundle (3 oneshot, 3 bounded, 1 unbounded) is repeated
  inline at each impl block that constructs channels through `C`:
  `ControlMessage<P, C>`, `Inner<P, F, S, Tm, R, C>`,
  `SendMessage<P, C>`, `SocketManager<MD, C>`, `Client<MD, R, I, C>`.
  A doc comment in `client::mod` explains why a single
  `C: ClientChannels<P>` trait alias does not work today (stable Rust
  does not elaborate where-clause bounds, and macros do not expand
  inside `where` clauses) and points at the implied-bounds RFC that
  would let it collapse.
- `ControlMessage`, `SendMessage`, `ReceivedMessage` go from
  `pub(super)` to `pub` and are re-exported from `client` so the
  forthcoming `static_channels!` macro can name them when generating
  per-`T` `*Pooled<MyChannels>` impls.
- `MessageDefinitions` gains a `Send` bound on every impl that bundles
  the channeled types — `Result<P, Error>: OneshotPooled<C>` requires
  `P: Send`. Already present on `Client`; added on `Inner` and the
  `SendMessage`/`ControlMessage` factory impls.

Verification:
- `cargo build` clean across `client-tokio`, `client+bare_metal+std`,
  `server`, all-features.
- `cargo test --all-features -- --test-threads=1`: 479 tests pass
  (457 lib + 11 client_server + 1 bare_metal_client + 1 bare_metal
  workspace member + 9 doctests).
- `cargo clippy --all-targets --all-features` clean.
- `cargo fmt -- --check` clean.

Collateral: `cargo fmt` swept up pre-existing baseline format drift
in `examples/`, `src/lib.rs`, `src/server/`, and one inner-test
match arm — included in this commit rather than split off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`src/static_channels/mod.rs` (~600 LOC) introduces no-alloc pool
primitives that back the upcoming `static_channels!` macro
(phase 13.6d). The module is gated on `feature = "bare_metal"` and
sits beside `crate::embassy_channels` (which still heap-alloc's
`Arc<Channel<...>>` per call).

Three families:

  - `OneshotSlot<T>` + `OneshotPool<T, POOL_SIZE>` →
    `StaticOneshotSender<T>` / `StaticOneshotReceiver<T>`
    implementing `OneshotSend<T>` / `OneshotRecv<T>`.

  - `MpscSlot<T, SLOT_CAP>` + `MpscPool<T, POOL_SIZE, SLOT_CAP>` →
    `StaticBoundedSender<T, SLOT_CAP>` /
    `StaticBoundedReceiver<T, SLOT_CAP>` implementing
    `MpscSend<T>` / `MpscRecv<T>`.

  - The same `MpscPool::claim_unbounded` returns
    `StaticUnboundedSender<T, SLOT_CAP>` /
    `StaticUnboundedReceiver<T, SLOT_CAP>` implementing
    `UnboundedSend<T>` / `UnboundedRecv<T>` (different trait
    semantics, same slot machinery — `SLOT_CAP` is the effective
    "unbounded" capacity).

Design notes baked into the doc comment:
- All slot/pool types have const `new()`, so a `static` array of
  slots initializes in const context (`[const { Slot::new() }; N]`,
  stable since 1.79).
- Free-list seeded lazily on the first `claim`. Operations are
  serialized via `embassy_sync::blocking_mutex::Mutex<CSRawMutex,
  Cell<usize>>`, sidestepping Treiber-stack ABA without giving up
  no-alloc.
- Slot-index recovery on release uses pointer arithmetic against
  the pool's `slots[0]` base — handles never carry the pool's
  `POOL_SIZE` const-generic. Reclaim hook is a `&'static dyn
  *Reclaim<T>` trait object on each handle (one vtable
  indirection per drop), erasing both `POOL_SIZE` and `SLOT_CAP`
  from the public sender/receiver types.
- Cancellation: oneshot sender drop sets a slot-level cancel bit
  and wakes a per-slot `AtomicWaker`; receiver's `recv()` future
  re-checks after registering both the cancel waker and the
  channel's internal waker (registered via a transient
  stack-pinned `chan.receive()` future, same trick the existing
  `EmbassySyncBoundedReceiver::poll_recv` uses). MPSC mirrors
  this: last-sender-drops sets `closed`, wakes the receiver,
  and `recv()` resolves to `None`.

Pool exhaustion semantics: `claim*()` returns `Option`. The
forthcoming `*Pooled::*_pair` impls cannot signal exhaustion
through their return type (the `ChannelFactory` trait's three
constructor methods return un-fallible pairs), so the macro
will `expect()` on `claim` — pool exhaustion becomes a panic
documented as a configuration error.

Receiver-drop-while-bounded-sender-blocked-on-full-channel is an
accepted v1 limitation. Documented in the module docstring.

Tests (7, all passing): oneshot send/recv happy path,
sender-drop cancels receiver, claim/release cycles back to a
fresh pool, pool exhaustion returns `None`, bounded send/recv,
clone-then-drop-all closes the receiver, unbounded `send_now`
returns `Err(value)` when the slot's fixed capacity is full.

What this leaves for 13.6d:
- The `static_channels!` declarative macro that takes a
  user-authored pool layout and emits per-`T`
  `*Pooled<MyChannels>` impls dispatching to declared `static`
  pools.

What this leaves for 13.6e:
- `tests/static_channels_witness.rs` — alloc-panicking
  `#[global_allocator]` shim verifying zero heap allocation
  after `Client::new` returns.
- `tests/bare_metal_client.rs` updated to use `StaticChannels`,
  dropping the `JoinHandle::abort` workaround once the
  end-of-life close semantics are exercised end-to-end.

Verification:
- `cargo build` clean: `bare_metal`, `client+bare_metal+std`,
  `client-tokio`, `server`, all-features.
- `cargo test --all-features -- --test-threads=1`: 486 tests
  pass (464 lib including the 7 new static_channels + 11
  client_server + 1 bare_metal_client + 1 bare_metal example +
  9 doctests).
- `cargo clippy --all-targets --all-features` clean.
- `cargo fmt -- --check` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Declarative macro that takes a user-authored pool layout and emits
the per-`T` `*Pooled<MyChannels>` impls + a `ChannelFactory` impl
on a unit struct. Lives in `src/static_channels/mod.rs` next to the
primitives, exported at crate root via `#[macro_export]`.

Macro grammar:

  define_static_channels! {
      name: MyChannels,
      oneshot: [
          (Result<(), MyError>, 80),
          (RebootResponse, 4),
      ],
      bounded: [
          ((ControlMessage<P, MyChannels>, 4), 1),
          ((SendMessage<P, MyChannels>, 16), 8),
      ],
      unbounded: [
          (ClientUpdate<P>, 1),
      ],
  }

Each entry is a tuple. Bounded uses `((T, slot_cap), pool_size)`
to let the `:ty` matcher disambiguate the type from the literals.
Unbounded entries take `(T, pool_size)` only — every unbounded
slot gets `UNBOUNDED_DEFAULT_CAP = 128` (matching the existing
embassy-sync default), exposed as a public const.

Generated impls:

  - One unit struct + `ChannelFactory` impl with associated types
    pointing at this module's `StaticOneshotSender` /
    `StaticBoundedSender<_, N>` / `StaticUnboundedSender<_, 128>`
    (and matching receivers).
  - One `OneshotPooled<$name> for T` impl per oneshot entry,
    each wrapping a function-local `static OneshotPool<T,
    POOL_SIZE>`. Function-scoped statics dodge name-collision
    across types without a `paste!` dep.
  - Same shape for `BoundedPooled<$name, SLOT_CAP> for T` and
    `UnboundedPooled<$name> for T`.
  - Pool exhaustion reaches the user as a panic with a stringified
    type name and pool-size in the message.

Tests (3 added, 10 in static_channels total):
- `macro_oneshot_dispatches_through_factory` — `MyChannels::oneshot::<u32>()`
  end-to-end through the `ChannelFactory` trait.
- `macro_bounded_dispatches_through_factory` — same for `bounded::<u8, 4>()`.
- `macro_unbounded_dispatches_through_factory` — same for
  `unbounded::<u16>()`.

What this leaves for 13.6e:
- `tests/static_channels_witness.rs` — alloc-panicking
  `#[global_allocator]` shim verifying zero heap allocation
  after `Client::new` returns, declaring the client's
  `MyChannels` via this macro.
- `tests/bare_metal_client.rs` updated to use a macro-declared
  `StaticChannels`, dropping the `JoinHandle::abort` workaround.

Verification:
- `cargo build` clean across `bare_metal`, `client+bare_metal+std`,
  `client-tokio`, `server`, all-features.
- `cargo test --all-features -- --test-threads=1`: 489 tests pass
  (467 lib including the 10 static_channels + 11 client_server
  + 1 bare_metal_client + 1 bare_metal example + 9 doctests).
- `cargo clippy --all-targets --all-features` clean.
- `cargo fmt -- --check` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…annels

Three changes:

1. **`tests/bare_metal_client.rs`** — switch from
   `EmbassySyncChannels` (heap-alloc per call) to a
   macro-declared `TestStaticChannels` via
   `define_static_channels!`. The Client integration test now
   exercises the static-pool channel path end-to-end. Pool sizes
   are deliberately small (oneshot pool=8/4/4, bounded pool=1/4/4,
   unbounded pool=1) — production firmware sizes pools to the
   workload's high-water mark.

2. **`tests/static_channels_alloc_witness.rs`** (new) — counting
   global allocator wired through `#[global_allocator]` plus two
   `#[tokio::test]`s that assert specific operations do not
   allocate after construction:
     - `no_alloc_when_claiming_oneshot_through_static_pool`:
       claim/send/release on a warmed-up pool allocates zero
       times.
     - `client_interface_read_after_construction_does_not_allocate`:
       16 successive `client.interface()` calls allocate zero
       times.
   Tests serialize over a `MEASURE_LOCK` to keep parallel test
   execution from interleaving allocations across measurement
   regions.

   This is a softer witness than the panicking-allocator harness
   the design memo specifies. The full panic-on-alloc gate
   requires (a) a no-alloc test executor (tokio's runtime
   allocates), (b) a no-alloc `Spawner` impl for per-socket
   loops, and (c) stack-based `E2ERegistryHandle` /
   `InterfaceHandle` impls. Each of those is real work and lives
   under phase 16's HighTec-target CI harness umbrella. The
   counting witness here catches per-call channel-storage
   regressions today; phase 16 catches everything else.

3. **`src/embassy_channels.rs` docstring** — point at
   `crate::static_channels` and `define_static_channels!` as the
   no-alloc bare-metal path; `EmbassySyncChannels` is now framed
   as the on-ramp for `std + alloc` integration.

`Cargo.toml` declares the new test under `required-features =
["client", "bare_metal"]`, matching `bare_metal_client`.

Verification:
- `cargo build` clean across `bare_metal`, `client+bare_metal+std`,
  `client-tokio`, `server`, all-features.
- `cargo test --all-features -- --test-threads=1`: 491 tests pass
  (467 lib + 11 client_server + 1 bare_metal_client + 2 alloc
  witness + 1 bare_metal example + 9 doctests).
- `cargo clippy --all-targets --all-features` clean.
- `cargo fmt -- --check` clean.

This concludes phase 13.6 — a 4-commit stack on
feature/phase13_6_static_channels:
  - 13.6a: const-N quirk fix (already on branch).
  - 13.6b: ChannelFactory per-T `*Pooled<C>` bounds.
  - 13.6c: src/static_channels/ pool primitives.
  - 13.6d: define_static_channels! macro.
  - 13.6e: this commit — alloc-counting witness + integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ous wake

- Add four missing tests: oneshot waker fires on send, oneshot cancel
  waker fires on sender drop, mpsc close waker fires when last sender
  drops, bounded-pool exhaustion returns None.
- Remove spurious cancel_waker.wake() from OneshotSend::send Ok branch;
  embassy-sync's channel waker already wakes the receiver on value
  arrival, making the cancel_waker call redundant.
- Add manual Debug impls for all ten public pool/handle types.
- Add #[derive(Debug)] to the struct generated by define_static_channels!.
- Accept optional vis: $vis:vis prefix in define_static_channels! via
  @Body delegation arm; callers without vis: default to pub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a static-pool, bare-metal–friendly ChannelFactory backend and updates the transport/channel abstraction so channel construction can be backed by per-type pools (enabling “no heap after Client::new” for channel storage).

Changes:

  • Added static_channels module providing OneshotPool/MpscPool primitives plus the define_static_channels! macro for generating per-T pooled channel wiring.
  • Reshaped transport::ChannelFactory to delegate constructors via OneshotPooled/BoundedPooled/UnboundedPooled and made bounded channel associated types capacity-typed (const N).
  • Updated client internals, embassy/tokio backends, and added witness tests for bare-metal/static-pool integration and allocation behavior.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/static_channels/mod.rs New static-pool channel primitives and define_static_channels! macro.
src/transport.rs Updates ChannelFactory to use pooled traits + const-capacity bounded types; adds pooled trait definitions/docs.
src/tokio_transport.rs Adapts TokioChannels to new pooled-trait + const-capacity Bounded* associated types.
src/embassy_channels.rs Adapts EmbassySyncChannels to new pooled-trait + const-capacity Bounded* associated types.
src/client/mod.rs Exposes message types for static pool declarations; adds required pooled bounds.
src/client/inner.rs Makes ControlMessage public and adds pooled bounds required by new factory shape.
src/client/socket_manager.rs Updates bounded channel types to ..., 16 and adds pooled bounds needed for construction.
src/lib.rs Exposes static_channels module under bare_metal; minor re-export cleanup.
tests/bare_metal_client.rs Switches witness test to macro-declared static channels backend.
tests/static_channels_alloc_witness.rs New integration test using a counting allocator to witness “no allocations” for static-pool hot-path ops.
Cargo.toml Registers new static_channels_alloc_witness integration test.
src/server/subscription_manager.rs Formatting-only change.
src/server/mod.rs Formatting-only test change.
examples/discovery_client/src/main.rs Formatting-only change.
examples/client_server/src/main.rs Formatting-only change.
examples/bare_metal/src/main.rs Formatting-only change.
.gitignore Updates ignored entries ordering/additions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +210 to +218
impl<T: Send + 'static> OneshotSend<T> for StaticOneshotSender<T> {
fn send(mut self, value: T) -> Result<(), T> {
match self.slot.chan.try_send(value) {
Ok(()) => {
self.sent = true;
Ok(())
}
Err(embassy_sync::channel::TrySendError::Full(v)) => Err(v),
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StaticOneshotSender::send doesn’t detect the receiver-drop case: OneshotSend::send is documented to return Err(value) if the receiver was already dropped, but this implementation only errors on TrySendError::Full (which shouldn’t happen for a fresh oneshot slot). This can report Ok(()) while the value is immediately dropped on slot reclaim.

Consider checking the slot’s O_RECEIVER_ALIVE bit (and/or maintaining an explicit “receiver dropped” flag) before/after try_send and returning Err(value) when the receiver is gone, to match the transport::OneshotSend contract.

Copilot uses AI. Check for mistakes.
Comment on lines +506 to +510
impl<T: Send + 'static, const SLOT_CAP: usize> MpscSend<T> for StaticBoundedSender<T, SLOT_CAP> {
async fn send(&self, value: T) -> Result<(), ()> {
self.slot.chan.send(value).await;
Ok(())
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StaticBoundedSender::send always returns Ok(()) and awaits Channel::send unconditionally. This violates the MpscSend contract in transport (must return Err(()) if the receiver was dropped) and can also hang indefinitely if the receiver is gone and the channel becomes full.

Consider integrating the slot’s closed flag into the send path (and waking blocked senders on receiver drop), so send() can fail fast with Err(()) when the receiver is dropped instead of always succeeding / potentially deadlocking.

Copilot uses AI. Check for mistakes.
Comment on lines +577 to +584
impl<T: Send + 'static, const SLOT_CAP: usize> UnboundedSend<T>
for StaticUnboundedSender<T, SLOT_CAP>
{
fn send_now(&self, value: T) -> Result<(), T> {
self.slot.chan.try_send(value).map_err(|e| match e {
embassy_sync::channel::TrySendError::Full(v) => v,
})
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StaticUnboundedSender::send_now doesn’t handle the receiver-dropped case. UnboundedSend::send_now is documented to return Err(value) if the receiver was dropped, but this implementation only returns Err on TrySendError::Full.

Consider checking slot.closed (set in StaticUnboundedReceiver::drop) and returning Err(value) when closed, so callers get the same semantics as the Tokio backend and values aren’t silently accepted then dropped on reclaim.

Copilot uses AI. Check for mistakes.
Comment thread src/client/mod.rs
/// declare static channel pools for it via
/// `crate::transport::BoundedPooled<C, 4>`. End users typically do not
/// reference this type directly — the
/// `crate::static_channels::static_channels!` macro names it for them.
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc comment references a crate::static_channels::static_channels! macro, but the macro introduced/used elsewhere in this PR is define_static_channels! (exported as crate::define_static_channels). This looks like a stale name and could mislead users trying to declare pools for ControlMessage.

Suggested change
/// `crate::static_channels::static_channels!` macro names it for them.
/// `crate::define_static_channels!` macro names it for them.

Copilot uses AI. Check for mistakes.
Comment thread src/lib.rs
pub mod embassy_channels;
/// Static-pool no-alloc primitives for [`transport::ChannelFactory`].
/// Backs the consumer-declared static `OneshotPool` / `MpscPool`
/// instances that the upcoming `static_channels!` macro (phase 13.6d)
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static_channels module doc mentions an “upcoming static_channels! macro”, but this PR exports define_static_channels! (via #[macro_export] in static_channels). If the macro has been renamed, updating this comment will prevent rustdoc users from searching for a non-existent macro name.

Suggested change
/// instances that the upcoming `static_channels!` macro (phase 13.6d)
/// instances that the `define_static_channels!` macro (phase 13.6d)

Copilot uses AI. Check for mistakes.
Comment thread src/transport.rs
/// publish a blanket `impl<T: Send + 'static> OneshotPooled<Self> for T`
/// (and its bounded / unbounded peers), so existing user code does not
/// notice the change. A static-pool backend instead publishes per-`T`
/// impls (typically generated by a `static_channels!` macro) that wire
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring mentions per-T impls “typically generated by a static_channels! macro”, but this PR exports define_static_channels! as the macro name. Updating the documentation to the correct macro name will make the compile-error guidance actionable for users.

Suggested change
/// impls (typically generated by a `static_channels!` macro) that wire
/// impls (typically generated by a `define_static_channels!` macro) that wire

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +22
//! This module hands out `&'static` references into pre-allocated
//! `static` pools instead. The user declares pools (typically via
//! the `static_channels!` macro in phase 13.6d) sized to their
//! workload's high-water mark; once seeded, no further allocation
//! occurs.
//!
//! # Per-`T` `*Pooled<MyChannels>` impls
//!
//! Phase 13.6b reshaped `ChannelFactory` so each constructor method
//! requires `T: *Pooled<Self>`. Static-pool consumers publish per-`T`
//! impls that route to the appropriate pool. The
//! `static_channels!` macro generates them; the primitives in this
//! module are the runtime they call into.
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docs refer to a static_channels! macro (lines 11–21), but the macro provided here is define_static_channels! (see later in this file). Renaming the references would help users find the right macro and avoid confusion when following the docs.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants