Skip to main content

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}