graphrefly_operators/binding.rs
1//! [`OperatorBinding`] — closure-registration helper trait for the
2//! operators crate. Sub-trait of [`BindingBoundary`] (D015).
3//!
4//! Operator factories ([`super::transform::map`], etc.) accept user
5//! closures of shape `Fn(T) -> R` / `Fn(T) -> bool` / `Fn(R, T) -> R`.
6//! At the FFI plane (per the handle-protocol cleaving plane invariant),
7//! Core only ever sees opaque [`HandleId`] integers. Bindings therefore
8//! need to wrap user closures into the `Fn(HandleId) -> HandleId` shape
9//! (and friends) — that wrapping is binding-side because it requires
10//! deref+intern operations against the binding's value registry, and
11//! returning a [`FnId`] that Core stores in the [`OperatorOp`] variant.
12//!
13//! Bindings impl both [`BindingBoundary`] (Core-callable FFI for
14//! `project_each` / `predicate_each` / `fold_each` / `pairwise_pack`) AND
15//! [`OperatorBinding`] (the closure-registration calls below).
16//!
17//! See [`OperatorOp`] for the per-operator FFI dispatch and `D016` in
18//! `docs/rust-port-decisions.md` for the wrapping discipline.
19//!
20//! [`BindingBoundary`]: graphrefly_core::BindingBoundary
21//! [`HandleId`]: graphrefly_core::HandleId
22//! [`FnId`]: graphrefly_core::FnId
23//! [`OperatorOp`]: graphrefly_core::OperatorOp
24
25use graphrefly_core::{BindingBoundary, FnId, HandleId};
26
27/// Closure-registration interface used by transform-operator factories.
28///
29/// Each method takes ownership of a user closure (boxed for type erasure)
30/// and returns the [`FnId`] under which the binding registered it. The
31/// operator factory then passes that `FnId` into [`graphrefly_core::OperatorOp`]
32/// so the wave engine's per-fire FFI calls can reach back through the
33/// registry.
34///
35/// # Closure shape
36///
37/// All closures take and return [`HandleId`] (or `bool` for predicates) —
38/// not `T` / `R`. Wrapping `Fn(T) -> R` into `Fn(HandleId) -> HandleId` is
39/// a binding-side concern: deref incoming handles to `T`, run user code,
40/// intern the output to a fresh `HandleId`. See `D016` for rationale.
41///
42/// Implementations must be `Send + Sync` for the closure storage so the
43/// binding can be shared across threads (matching `BindingBoundary`'s
44/// `Send + Sync` super-bounds).
45pub trait OperatorBinding: BindingBoundary {
46 /// Register a single-arg projector: `Fn(T) -> R` wrapped into
47 /// `Fn(HandleId) -> HandleId`. Used by [`super::transform::map`].
48 /// The returned [`FnId`] is passed to
49 /// [`graphrefly_core::OperatorOp::Map`].
50 fn register_projector(&self, f: Box<dyn Fn(HandleId) -> HandleId + Send + Sync>) -> FnId;
51
52 /// Register a single-arg predicate: `Fn(T) -> bool`. Used by
53 /// [`super::transform::filter`]. The returned [`FnId`] is passed to
54 /// [`graphrefly_core::OperatorOp::Filter`].
55 fn register_predicate(&self, f: Box<dyn Fn(HandleId) -> bool + Send + Sync>) -> FnId;
56
57 /// Register a left-fold reducer: `Fn(R, T) -> R` wrapped into
58 /// `Fn(HandleId, HandleId) -> HandleId`. Used by
59 /// [`super::transform::scan`] and [`super::transform::reduce`]. The
60 /// returned [`FnId`] is passed to [`graphrefly_core::OperatorOp::Scan`]
61 /// or [`graphrefly_core::OperatorOp::Reduce`].
62 fn register_folder(&self, f: Box<dyn Fn(HandleId, HandleId) -> HandleId + Send + Sync>)
63 -> FnId;
64
65 /// Register a custom-equals oracle: `Fn(T, T) -> bool` reused via
66 /// [`BindingBoundary::custom_equals`]. Used by
67 /// [`super::transform::distinct_until_changed`]. The returned
68 /// [`FnId`] is passed to
69 /// [`graphrefly_core::OperatorOp::DistinctUntilChanged`].
70 fn register_equals(&self, f: Box<dyn Fn(HandleId, HandleId) -> bool + Send + Sync>) -> FnId;
71
72 /// Register a pairwise packer: `Fn(prev: T, current: T) -> (T, T)`
73 /// wrapped into `Fn(HandleId, HandleId) -> HandleId` (where the
74 /// returned handle resolves to the binding's tuple representation).
75 /// Used by [`super::transform::pairwise`]. The returned [`FnId`] is
76 /// passed to [`graphrefly_core::OperatorOp::Pairwise`].
77 fn register_pairwise_packer(
78 &self,
79 f: Box<dyn Fn(HandleId, HandleId) -> HandleId + Send + Sync>,
80 ) -> FnId;
81
82 /// Register a tuple packer: `Fn(&[T]) -> Tuple` wrapped into
83 /// `Fn(&[HandleId]) -> HandleId` (where the returned handle resolves
84 /// to the binding's N-ary tuple representation).
85 /// Used by [`super::combine::combine`] and
86 /// [`super::combine::with_latest_from`]. The returned [`FnId`] is
87 /// passed to [`graphrefly_core::OperatorOp::Combine`] or
88 /// [`graphrefly_core::OperatorOp::WithLatestFrom`].
89 fn register_packer(&self, f: super::combine::PackerFn) -> FnId;
90
91 /// Register a side-effect callback: `Fn(T)` wrapped into
92 /// `Fn(HandleId)`. Used by [`super::control::tap`] and
93 /// [`super::control::on_first_data`]. The returned [`FnId`] is passed
94 /// to [`graphrefly_core::OperatorOp::Tap`] or
95 /// [`graphrefly_core::OperatorOp::TapFirst`]. The binding invokes
96 /// the stored closure from
97 /// [`BindingBoundary::invoke_tap_fn`](graphrefly_core::BindingBoundary::invoke_tap_fn).
98 fn register_tap(&self, _f: Box<dyn Fn(HandleId) + Send + Sync>) -> FnId {
99 unimplemented!("register_tap: this binding does not support control operators")
100 }
101
102 /// Register an error-recovery callback: `Fn(HandleId) -> HandleId`.
103 /// Used by [`super::control::rescue`]. The binding invokes the stored
104 /// closure from
105 /// [`BindingBoundary::invoke_rescue_fn`](graphrefly_core::BindingBoundary::invoke_rescue_fn).
106 fn register_rescue(
107 &self,
108 _f: Box<dyn Fn(HandleId) -> Result<HandleId, ()> + Send + Sync>,
109 ) -> FnId {
110 unimplemented!("register_rescue: this binding does not support control operators")
111 }
112
113 /// Register a stratify classifier: `Fn(rules: HandleId, value:
114 /// HandleId) -> bool`. Used by
115 /// [`super::stratify::stratify_branch`] (D199). The binding-side
116 /// closure typically captures the branch name and looks up the
117 /// matching rule inside the rules array before running the rule's
118 /// `classify(value)` predicate. The binding invokes the stored
119 /// closure from
120 /// [`BindingBoundary::invoke_stratify_classifier_fn`](graphrefly_core::BindingBoundary::invoke_stratify_classifier_fn).
121 fn register_stratify_classifier(
122 &self,
123 _f: Box<dyn Fn(HandleId, HandleId) -> bool + Send + Sync>,
124 ) -> FnId {
125 unimplemented!("register_stratify_classifier: this binding does not support the stratify operator (D199)")
126 }
127}