mailbox: Phase 4b — send_dyn bare-pid escape hatch (RFC 014 §4.6)

The explicit fallible fallback for when only an untyped Pid is in hand (off a
Down, out of a future members()), so the typed send_to / send-by-name stay the
default and callers don't grow workarounds around a missing primitive.

- send_dyn::<M>(Pid, msg): one line over the shared send_to_pid core. Same
  identity-bound, no-redirect liveness as send_to (Dead once the incarnation is
  gone); the difference is the explicit M, so NoChannel is a real runtime
  outcome here rather than the debug_assert it collapses to on the typed paths.
- Takes Pid<Erased> specifically: a typed Pid<A> must .erase() to reach it, so
  opting into the fallible downcast stays visible at the call site.

tests/registry.rs: send_dyn_delivers_and_reports_wrong_type (right type
delivers; live actor with no channel for the asked type -> NoChannel) and
send_dyn_to_dead_pid_is_dead. Full suite + order-checker green, warning-free.

This completes the RFC 014 send surface: Name<M> (re-resolving), Pid<A>
(typed direct), send_dyn (bare-pid). send_after (§6) can now target any of
them — its Dest question is answered.
This commit is contained in:
smarm-agent
2026-06-17 11:45:48 +00:00
parent 3cec3ba1a1
commit 4c56938f0b
3 changed files with 63 additions and 3 deletions
+42 -2
View File
@@ -6,8 +6,8 @@
//! register-then-send race without busy-waiting on `whereis`.
use smarm::{
channel, install, register, run, send, send_to, spawn, unregister, whereis, Addressable, Name,
Pid, RegisterError, SendError,
channel, install, register, run, send, send_dyn, send_to, spawn, unregister, whereis,
Addressable, Name, Pid, RegisterError, SendError,
};
const SVC: Name<u64> = Name::new("svc");
@@ -209,3 +209,43 @@ fn send_to_does_not_redirect_after_takeover() {
b.join().unwrap();
});
}
// --- RFC 014 §4.6: explicit bare-pid escape hatch ---------------------------
#[test]
fn send_dyn_delivers_and_reports_wrong_type() {
run(|| {
let (ready_tx, ready_rx) = channel::<()>();
let (done_tx, done_rx) = channel::<()>();
let (tx, rx) = channel::<u64>();
let h = spawn(move || {
register(Name::<u64>::new("dyn"), tx).unwrap(); // publishes a u64 channel
ready_tx.send(()).unwrap();
assert_eq!(rx.recv().unwrap(), 3);
done_rx.recv().unwrap(); // stay alive for the wrong-type probe
});
ready_rx.recv().unwrap();
let p = h.pid(); // a bare Pid<Erased>, as if recovered off a Down
send_dyn::<u64>(p, 3u64).unwrap(); // right type: delivered
// Live actor, but it has no channel for &str — the genuinely-fallible case.
assert!(matches!(send_dyn::<&'static str>(p, "nope"), Err(SendError::NoChannel(_))));
done_tx.send(()).unwrap();
h.join().unwrap();
});
}
#[test]
fn send_dyn_to_dead_pid_is_dead() {
run(|| {
let (ready_tx, ready_rx) = channel::<()>();
let (tx, _rx) = channel::<u64>();
let h = spawn(move || {
register(Name::<u64>::new("dyn2"), tx).unwrap();
ready_tx.send(()).unwrap(); // then return -> die
});
ready_rx.recv().unwrap();
let p = h.pid();
h.join().unwrap(); // dead; the bare pid now names a dead incarnation
assert!(matches!(send_dyn::<u64>(p, 1u64), Err(SendError::Dead(_))));
});
}