dbsp/circuit/operator_traits.rs
1//! Common traits for DBSP operators.
2//!
3//! Operators are the building blocks of DBSP circuits. An operator
4//! consumes one or more input streams and produces an output stream.
5
6#![allow(async_fn_in_trait)]
7
8use arc_swap::ArcSwap;
9use feldera_storage::{FileCommitter, StoragePath};
10
11use crate::Error;
12use crate::{
13 circuit::{
14 OwnershipPreference, Scope,
15 metadata::{OperatorLocation, OperatorMeta},
16 },
17 trace::cursor::Position,
18};
19use std::borrow::Cow;
20use std::fmt::Display;
21use std::sync::Arc;
22
23use super::GlobalNodeId;
24
25/// Minimal requirements for values exchanged by operators.
26pub trait Data: Clone + 'static {}
27
28impl<T: Clone + 'static> Data for T {}
29
30/// Trait that must be implemented by all operators.
31pub trait Operator: 'static {
32 /// Human-readable operator name for debugging purposes.
33 fn name(&self) -> Cow<'static, str>;
34
35 /// The location the operator was created at
36 fn location(&self) -> OperatorLocation {
37 None
38 }
39
40 /// Initialize the operator
41 fn init(&mut self, _global_id: &GlobalNodeId) {}
42
43 /// Collects metadata about the current operator
44 fn metadata(&self, _meta: &mut OperatorMeta) {}
45
46 /// Notify the operator about the start of a new clock epoch.
47 ///
48 /// `clock_start` and `clock_end` methods support the nested circuit
49 /// architecture. A nested circuit (or subcircuit) is a node in
50 /// the parent circuit that contains another circuit. The nested circuit
51 /// has its own clock. Each parent clock tick starts a new child clock
52 /// epoch. Each operator gets notified about start and end of a clock
53 /// epoch in its local circuit and all of its ancestors.
54 ///
55 /// Formally, operators in a nested circuit operate over nested streams,
56 /// or streams of streams, with each nested clock epoch starting a new
57 /// stream. Thus the `clock_start` and `clock_end` methods signal
58 /// respectively the start and completion of a nested stream.
59 ///
60 /// # Examples
61 ///
62 /// For example, feeding the following matrix, where rows represent nested
63 /// streams,
64 ///
65 /// ```text
66 /// ┌ ┐
67 /// │1 2 │
68 /// │3 4 5 6│
69 /// │7 8 9 |
70 /// └ ┘
71 /// ```
72 ///
73 /// to an operator requires the following sequence of invocations
74 ///
75 /// ```text
76 /// clock_start(1) // Start outer clock.
77 /// clock_start(0) // Start nested clock (first row of the matrix).
78 /// eval(1)
79 /// eval(2)
80 /// clock_end(0) // End nested clock.
81 /// clock_start(0) // Start nested clock (second row).
82 /// eval(3)
83 /// eval(4)
84 /// eval(5)
85 /// eval(6)
86 /// clock_end(0) // End nested clock.
87 /// clock_start(0) // Start nested clock (third row).
88 /// eval(7)
89 /// eval(8)
90 /// eval(9)
91 /// clock_end(0) // End nested clock.
92 /// clock_end(1) // End outer clock.
93 /// ```
94 ///
95 /// Note that the input and output of most operators belong to the same
96 /// clock domain, i.e., an operator cannot consume a single value and
97 /// produce a stream, or the other way around. The only exception are
98 /// [`ImportOperator`]s that make the contents of a stream in the parent
99 /// circuit available inside a subcircuit.
100 ///
101 /// An operator can have multiple input streams, all of which belong to the
102 /// same clock domain and therefore start and end at the same time. Hence
103 /// `clock_start` and `clock_end` apply to all input and output streams of
104 /// the operator.
105 ///
106 /// # Arguments
107 ///
108 /// * `scope` - the scope whose clock is restarting.
109 fn clock_start(&mut self, _scope: Scope) {}
110 fn clock_end(&mut self, _scope: Scope) {}
111
112 /// Returns `true` if `self` is an asynchronous operator.
113 ///
114 /// An asynchronous operator may need to wait for external inputs, i.e.,
115 /// inputs from outside the circuit. While a regular synchronous
116 /// operator is ready to be triggered as soon as all of its input
117 /// streams contain data, an async operator may require additional
118 /// inputs that arrive asynchronously with respect to the operation of
119 /// the circuit (e.g., from an I/O device or via an IPC channel).
120 ///
121 /// We do not allow operators to block, therefore the scheduler must not
122 /// schedule an async operator until it has all external inputs
123 /// available. The scheduler checks that the operator is ready to
124 /// execute using the [`ready`](`Self::ready`) method.
125 fn is_async(&self) -> bool {
126 false
127 }
128
129 /// Returns `true` if `self` is an input operator.
130 ///
131 /// An input operator feeds new data into the circuit. Examples are
132 /// the `Input` and `Generator` operators.
133 fn is_input(&self) -> bool {
134 false
135 }
136
137 /// Returns `true` if `self` has received all required external inputs and
138 /// is ready to run.
139 ///
140 /// This method must always returns `true` for synchronous operators. For
141 /// an asynchronous operator, it returns `true` if the operator has all
142 /// external inputs available (see [`is_async`](`Self::is_async`)
143 /// documentation). Once the operator is ready, it remains ready within
144 /// the current clock cycle, thus the scheduler can safely evaluate the
145 /// operator.
146 fn ready(&self) -> bool {
147 true
148 }
149
150 /// Register callback to be invoked when an asynchronous operator becomes
151 /// ready.
152 ///
153 /// This method should only be used for asynchronous operators (see
154 /// documentation for [`is_async`](`Self::is_async`) and
155 /// [`ready`](`Self::ready`)) in order to enable dynamic schedulers to
156 /// run async operators as they become ready without continuously
157 /// polling them. The operator need only support this method being
158 /// called once, to set a single callback.
159 ///
160 /// Once the callback has been registered, the operator will invoke the
161 /// callback at every clock cycle, when the operator becomes ready.
162 /// The callback is invoked with at-least-once semantics, meaning that
163 /// spurious invocations are possible. The scheduler must always check
164 /// if the operator is ready to run by calling [`ready`](`Self::ready`)
165 /// and must be prepared to wait if it returns `false`.
166 fn register_ready_callback<F>(&mut self, _cb: F)
167 where
168 F: Fn() + Send + Sync + 'static,
169 {
170 }
171
172 /// Check if the operator is in a stable state.
173 ///
174 /// This method is invoked as part of checking if the circuit has reached a
175 /// fixed point state, i.e., a state where the outputs of all operators will
176 /// remain constant until the end of the current clock epoch
177 /// (see [`Circuit::fixedpoint`](`crate::circuit::Circuit::fixedpoint`)).
178 ///
179 /// It returns `true` if the operator's output is guaranteed to remain
180 /// constant (i.e., all future outputs will be equal to the last output) as
181 /// long as its inputs remain constant.
182 ///
183 /// The exact semantics depends on the value of the `scope` argument, which
184 /// identifies the circuit whose fixed point state is being checked.
185 /// Scope 0 is the local circuit. The method is invoked with `scope=0`
186 /// at the end of a clock cycle, and should return `true` if, assuming that
187 /// it will see inputs identical to the last input during all future clock
188 /// cycles in the current clock epoch, it will keep producing the same
189 /// outputs.
190 ///
191 /// Scope 1 represents the parent of the local circuit. The method is
192 /// invoked with `scope=1` at the end of a clock _epoch_, and should
193 /// return `true` if, assuming that it will see a sequence of inputs
194 /// (aka the input stream) identical to the last epoch during all future
195 /// epochs, it will keep producing the same output streams.
196 ///
197 /// Scope 2 represents the grandparent of the local circuit. The method is
198 /// invoked with `scope=2` at the end of the parent clock _epoch_, and
199 /// checks that the operator's output will remain stable wrt to the
200 /// nested input stream (i.e., stream of streams).
201 ///
202 /// And so on.
203 ///
204 /// The check must be precise. False positives (returning `true` when the
205 /// output may change in the future) may lead to early termination before
206 /// the circuit has reached a fixed point (and hence incorrect output).
207 /// False negatives (returning `false` in a stable state) is only acceptable
208 /// for a finite number of clock cycles and will otherwise prevent the
209 /// fixedpoint computation from converging.
210 ///
211 /// # Warning
212 ///
213 /// Two operators currently violate this requirement:
214 /// [`Z1`](`crate::operator::Z1`) and
215 /// [`Z1Nested`](`crate::operator::Z1Nested`). The latter will get phased
216 /// out soon. The former is work-in-progress. It can be safely used inside
217 /// nested circuits when carrying changes to collections across iterations
218 /// of the fixed point computation, but not as part of an integrator circuit
219 /// ([`Stream::integrate`](`crate::circuit::Stream::integrate`)).
220 fn fixedpoint(&self, scope: Scope) -> bool;
221
222 /// Instructs the operator to checkpoint its state to persistent storage in
223 /// directory `base`. Any files that the operator creates should have
224 /// `persistent_id` in their names to keep them unique.
225 ///
226 /// The operator shouldn't commit the state to stable storage; rather, it
227 /// should append the files to be committed to `files` for later commit.
228 ///
229 /// For most operators this method is a no-op.
230 ///
231 /// Fails if the operator is stateful, i.e., expects a checkpoint, by
232 /// `persistent_id` is `None`
233 #[allow(unused_variables)]
234 fn checkpoint(
235 &mut self,
236 base: &StoragePath,
237 persistent_id: Option<&str>,
238 files: &mut Vec<Arc<dyn FileCommitter>>,
239 ) -> Result<(), Error> {
240 Ok(())
241 }
242
243 /// Instruct the operator to restore its state from persistent storage in
244 /// directory `base`, using `persistent_id` to find its files.
245 ///
246 /// For most operators this method is a no-op.
247 #[allow(unused_variables)]
248 fn restore(&mut self, base: &StoragePath, persistent_id: Option<&str>) -> Result<(), Error> {
249 Ok(())
250 }
251
252 /// Clear the operator's state.
253 fn clear_state(&mut self) -> Result<(), Error> {
254 Ok(())
255 }
256
257 /// Start replaying the operator's state to the replay stream.
258 ///
259 /// Only defined for operators that support replay.
260 fn start_replay(&mut self) -> Result<(), Error> {
261 panic!("start_replay() is not implemented for this operator")
262 }
263
264 /// Check if the operator has finished replaying its state.
265 ///
266 /// Only defined for operators that support replay.
267 fn is_replay_complete(&self) -> bool {
268 panic!("is_replay_complete() is not implemented for this operator")
269 }
270
271 /// Cleanup any state needed for replay and prepare the operator for normal operation.
272 ///
273 /// Only defined for operators that support replay.
274 fn end_replay(&mut self) -> Result<(), Error> {
275 panic!("end_replay() is not implemented for this operator")
276 }
277
278 /// Notify the operator about start of a transaction.
279 ///
280 /// The operator can initialize any state needed for the transaction.
281 fn start_transaction(&mut self) {}
282
283 /// Notifies the operator that all of its predecessors have produced
284 /// all outputs for the current transaction.
285 ///
286 /// Operators that wait for all inputs to arrive before producing
287 /// outputs (e.g., join, aggregate, etc.) can use this notification to
288 /// start processing inputs the next time `eval` is invoked.
289 fn flush(&mut self) {}
290
291 /// Invoked after `flush` after each `eval` call to check if all outputs
292 /// have been produced. Because it is invoked only after calling `eval`,
293 /// every operator must produce at least one output.
294 ///
295 /// Once this method returns `true`, its downstream operators can be flushed.
296 fn is_flush_complete(&self) -> bool {
297 true
298 }
299
300 /// Returns the current progress of the operator in processing the current transaction.
301 ///
302 /// Returns a best-effort estimate of the amount of work done by the operator
303 /// toward processing inputs accumulated before `flush` was called.
304 ///
305 /// Can return `None` if the operator is not in flush mode (i.e., between
306 /// `flush` was called and `is_flush_complete` returns `true`).
307 fn flush_progress(&self) -> Option<Position> {
308 None
309 }
310
311 /// Start compaction of the operator's state.
312 ///
313 /// Only defined for operators that support compaction. No-op for all other operators.
314 fn start_compaction(&mut self) {}
315}
316
317/// A source operator that injects data from the outside world or from the
318/// parent circuit into the local circuit. Consumes no input streams and emits
319/// a single output stream.
320pub trait SourceOperator<O>: Operator {
321 /// Yield the next value.
322 async fn eval(&mut self) -> O;
323}
324
325/// A sink operator consumes an input stream, but does not produce an output
326/// stream. Such operators are used to send results of the computation
327/// performed by the circuit to the outside world.
328pub trait SinkOperator<I>: Operator {
329 /// Consume input by reference.
330 async fn eval(&mut self, input: &I);
331
332 /// Consume input by value.
333 async fn eval_owned(&mut self, input: I) {
334 self.eval(&input).await
335 }
336
337 /// Ownership preference on the operator's input stream
338 /// (see [`OwnershipPreference`]).
339 fn input_preference(&self) -> OwnershipPreference {
340 OwnershipPreference::INDIFFERENT
341 }
342}
343
344/// A sink operator that consumes two input streams, but does not produce
345/// an output stream. Such operators are used to send results of the
346/// computation performed by the circuit to the outside world.
347pub trait BinarySinkOperator<I1, I2>: Operator
348where
349 I1: Clone,
350 I2: Clone,
351{
352 /// Consume inputs.
353 ///
354 /// The operator must be prepared to handle any combination of
355 /// owned and borrowed inputs.
356 async fn eval<'a>(&mut self, lhs: Cow<'a, I1>, rhs: Cow<'a, I2>);
357
358 /// Ownership preference on the operator's input streams
359 /// (see [`OwnershipPreference`]).
360 fn input_preference(&self) -> (OwnershipPreference, OwnershipPreference) {
361 (
362 OwnershipPreference::INDIFFERENT,
363 OwnershipPreference::INDIFFERENT,
364 )
365 }
366}
367
368/// A sink operator that consumes three input streams, but does not produce
369/// an output stream. Such operators are used to send results of the
370/// computation performed by the circuit to the outside world.
371pub trait TernarySinkOperator<I1, I2, I3>: Operator
372where
373 I1: Clone,
374 I2: Clone,
375 I3: Clone,
376{
377 /// Consume inputs.
378 ///
379 /// The operator must be prepared to handle any combination of
380 /// owned and borrowed inputs.
381 async fn eval<'a>(&mut self, input1: Cow<'a, I1>, input2: Cow<'a, I2>, input3: Cow<'a, I3>);
382
383 /// Ownership preference on the operator's input streams
384 /// (see [`OwnershipPreference`]).
385 fn input_preference(
386 &self,
387 ) -> (
388 OwnershipPreference,
389 OwnershipPreference,
390 OwnershipPreference,
391 ) {
392 (
393 OwnershipPreference::INDIFFERENT,
394 OwnershipPreference::INDIFFERENT,
395 OwnershipPreference::INDIFFERENT,
396 )
397 }
398}
399
400/// A unary operator that consumes a stream of inputs of type `I`
401/// and produces a stream of outputs of type `O`.
402pub trait UnaryOperator<I, O>: Operator {
403 /// Consume input by reference.
404 async fn eval(&mut self, input: &I) -> O;
405
406 /// Consume input by value.
407 async fn eval_owned(&mut self, input: I) -> O {
408 self.eval(&input).await
409 }
410
411 /// Ownership preference on the operator's input stream
412 /// (see [`OwnershipPreference`]).
413 fn input_preference(&self) -> OwnershipPreference {
414 OwnershipPreference::INDIFFERENT
415 }
416}
417
418/// A binary operator consumes two input streams carrying values
419/// of types `I1` and `I2` and produces a stream of outputs of type `O`.
420pub trait BinaryOperator<I1, I2, O>: Operator {
421 /// Consume input by reference.
422 async fn eval(&mut self, lhs: &I1, rhs: &I2) -> O;
423
424 /// Consume input by value.
425 async fn eval_owned(&mut self, lhs: I1, rhs: I2) -> O {
426 self.eval(&lhs, &rhs).await
427 }
428
429 /// Consume the first input by value and the second by reference.
430 async fn eval_owned_and_ref(&mut self, lhs: I1, rhs: &I2) -> O {
431 self.eval(&lhs, rhs).await
432 }
433
434 /// Consume the first input by reference and the second by value.
435 async fn eval_ref_and_owned(&mut self, lhs: &I1, rhs: I2) -> O {
436 self.eval(lhs, &rhs).await
437 }
438
439 /// Ownership preference on the operator's input streams
440 /// (see [`OwnershipPreference`]).
441 fn input_preference(&self) -> (OwnershipPreference, OwnershipPreference) {
442 (
443 OwnershipPreference::INDIFFERENT,
444 OwnershipPreference::INDIFFERENT,
445 )
446 }
447}
448
449/// A ternary operator consumes three input streams carrying values
450/// of types `I1`, `I2`, and `I3` and produces a stream of outputs of type `O`.
451pub trait TernaryOperator<I1, I2, I3, O>: Operator
452where
453 I1: Clone,
454 I2: Clone,
455 I3: Clone,
456{
457 /// Consume inputs.
458 ///
459 /// The operator must be prepared to handle any combination of
460 /// owned and borrowed inputs.
461 async fn eval(&mut self, i1: Cow<'_, I1>, i2: Cow<'_, I2>, i3: Cow<'_, I3>) -> O;
462
463 fn input_preference(
464 &self,
465 ) -> (
466 OwnershipPreference,
467 OwnershipPreference,
468 OwnershipPreference,
469 ) {
470 (
471 OwnershipPreference::INDIFFERENT,
472 OwnershipPreference::INDIFFERENT,
473 OwnershipPreference::INDIFFERENT,
474 )
475 }
476}
477
478/// A quaternary operator consumes four input streams carrying values
479/// of types `I1`, `I2`, `I3`, and `I4` and produces a stream of outputs of type
480/// `O`.
481pub trait QuaternaryOperator<I1, I2, I3, I4, O>: Operator
482where
483 I1: Clone,
484 I2: Clone,
485 I3: Clone,
486 I4: Clone,
487{
488 /// Consume inputs.
489 ///
490 /// The operator must be prepared to handle any combination of
491 /// owned and borrowed inputs.
492 async fn eval(
493 &mut self,
494 i1: Cow<'_, I1>,
495 i2: Cow<'_, I2>,
496 i3: Cow<'_, I3>,
497 i4: Cow<'_, I4>,
498 ) -> O;
499
500 fn input_preference(
501 &self,
502 ) -> (
503 OwnershipPreference,
504 OwnershipPreference,
505 OwnershipPreference,
506 OwnershipPreference,
507 ) {
508 (
509 OwnershipPreference::INDIFFERENT,
510 OwnershipPreference::INDIFFERENT,
511 OwnershipPreference::INDIFFERENT,
512 OwnershipPreference::INDIFFERENT,
513 )
514 }
515}
516
517/// An operator that consumes any number of streams carrying values
518/// of type `I` and produces a stream of outputs of type `O`.
519pub trait NaryOperator<I, O>: Operator
520where
521 I: Clone + 'static,
522{
523 /// Consume inputs.
524 ///
525 /// The operator must be prepared to handle any combination of
526 /// owned and borrowed inputs.
527 async fn eval<'a, Iter>(&'a mut self, inputs: Iter) -> O
528 where
529 Iter: Iterator<Item = Cow<'a, I>>;
530
531 /// Ownership preference on the operator's input streams
532 /// (see [`OwnershipPreference`]).
533 fn input_preference(&self) -> OwnershipPreference {
534 OwnershipPreference::INDIFFERENT
535 }
536}
537
538/// A "strict operator" is one whose output only depends on inputs from previous
539/// timestamps and hence can be produced before consuming new inputs. This way
540/// a strict operator can be used as part of a feedback loop where its output is
541/// needed before input for the current timestamp is available.
542///
543/// The only strict operators that DBSP makes available are [Z1] and its variant
544/// [Z1Nested].
545///
546/// [Z1]: crate::operator::Z1
547/// [Z1Nested]: crate::operator::Z1Nested
548/// [Z1Trace]: crate::operator::dynamic::trace::Z1Trace
549pub trait StrictOperator<O>: Operator {
550 /// Returns the output value computed based on data consumed by the operator
551 /// during previous timestamps. This method is invoked **before**
552 /// `eval_strict()` has been invoked for the current timestamp. It can
553 /// be invoked **at most once** for each timestamp,
554 /// as the implementation may mutate or destroy the operator's internal
555 /// state (for example [Z1](`crate::operator::Z1`) returns its inner
556 /// value, leaving the operator empty).
557 fn get_output(&mut self) -> O;
558
559 fn get_final_output(&mut self) -> O;
560}
561
562/// A strict unary operator that consumes a stream of inputs of type `I`
563/// by reference and produces a stream of outputs of type `O`.
564pub trait StrictUnaryOperator<I, O>: StrictOperator<O> {
565 /// Feed input for the current timestamp to the operator by reference. The
566 /// output will be consumed via
567 /// [`get_output`](`StrictOperator::get_output`) during the
568 /// next timestamp.
569 async fn eval_strict(&mut self, input: &I);
570
571 /// Feed input for the current timestamp to the operator by value. The
572 /// output will be consumed via
573 /// [`get_output`](`StrictOperator::get_output`) during the
574 /// next timestamp.
575 async fn eval_strict_owned(&mut self, input: I) {
576 self.eval_strict(&input).await
577 }
578
579 /// Ownership preference on the operator's input stream
580 /// (see [`OwnershipPreference`]).
581 fn input_preference(&self) -> OwnershipPreference {
582 OwnershipPreference::INDIFFERENT
583 }
584
585 /// Flush the input half of the strict operator.
586 ///
587 /// The strict operator appears in the circuit twice: as a source
588 /// operator that outputs the delayed value at the start of the step,
589 /// and as a sink operator that consumes the new value.
590 ///
591 /// These operators are flushed separately. The `Operator::flush` and
592 /// `Operator::is_flush_complete` methods are invoked on the output half.
593 ///
594 /// The `flush_input` and `is_flush_input_complete` methods are invoked
595 /// on the input half.
596 fn flush_input(&mut self);
597
598 /// See [`StrictUnaryOperator::flush_input`] for more details.
599 fn is_flush_input_complete(&self) -> bool;
600}
601
602/// An import operator makes a stream from the parent circuit
603/// available inside a subcircuit.
604///
605/// Import operators are the only kind of operator that span
606/// two clock domains: an import operator reads a single
607/// value from the parent stream per parent clock tick and produces
608/// a stream of outputs in the nested circuit, one for each nested
609/// clock tick.
610///
611/// See [`Delta0`](`crate::operator::Delta0`) for a concrete example
612/// of an import operator.
613pub trait ImportOperator<I, O>: Operator {
614 /// Consumes a value from the parent stream by reference.
615 ///
616 /// Either `import` or [`Self::import_owned`] is invoked once per
617 /// nested clock epoch, right after `clock_start(0)`.
618 async fn import(&mut self, val: &I);
619
620 /// Consumes a value from the parent stream by value.
621 async fn import_owned(&mut self, val: I);
622
623 /// Invoked once per nested clock cycle to write a value to
624 /// the output stream.
625 async fn eval(&mut self) -> O;
626
627 /// Ownership preference on the operator's input stream
628 /// (see [`OwnershipPreference`]).
629 fn input_preference(&self) -> OwnershipPreference {
630 OwnershipPreference::INDIFFERENT
631 }
632}
633
634/// The name of an operator, primarily for use in CPU profiles.
635///
636/// An operator doesn't initially know its global node ID, but the global node
637/// ID is useful for debugging and profiling. This type allows the name to
638/// initially omit the ID but adds it when it becomes available.
639pub struct OperatorName(ArcSwap<String>);
640
641impl OperatorName {
642 /// Creates a new `OperatorName` that doesn't initially know the global node
643 /// ID, since an operator doesn't know this until [Operator::init] is
644 /// called.
645 ///
646 /// If the name is fetched, with [OperatorName::get], before
647 /// [OperatorName::init], then it will just returned as simply `(base)`.
648 pub fn new(base: &str) -> Self {
649 Self(ArcSwap::new(Arc::new(format!("({base})"))))
650 }
651
652 /// Updates the name to include `global_id`. This should normally be called
653 /// once, from the implementation of [Operator::init]. If it is called more
654 /// than once then later calls are ignored.
655 ///
656 /// After this function is called, the name will have the format `base
657 /// nodeid` where `nodeid` is in the format used by the circuit profiler so
658 /// that it's easy to find by searching.
659 pub fn init(&self, global_id: &GlobalNodeId) {
660 let s = self.0.load();
661 if let Some(rest) = s.strip_prefix('(')
662 && let Some(base) = rest.strip_suffix(')')
663 {
664 self.0
665 .store(Arc::new(format!("{base} {}", global_id.node_identifier())));
666 }
667 }
668
669 /// Returns a copy of the operator name string.
670 pub fn get(&self) -> Arc<String> {
671 self.0.load_full()
672 }
673}
674
675impl Display for OperatorName {
676 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
677 f.write_str(&self.0.load())
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use crate::circuit::{GlobalNodeId, NodeId, operator_traits::OperatorName};
684
685 #[test]
686 fn operator_name() {
687 let name = OperatorName::new("Name");
688 assert_eq!(name.to_string(), "(Name)");
689 name.init(&GlobalNodeId::from_path(&[
690 NodeId::new(1),
691 NodeId::new(2),
692 NodeId::new(3),
693 ]));
694 assert_eq!(name.to_string(), "Name nn1_n2_n3");
695 name.init(&GlobalNodeId::from_path(&[
696 NodeId::new(4),
697 NodeId::new(5),
698 NodeId::new(6),
699 ]));
700 assert_eq!(name.to_string(), "Name nn1_n2_n3");
701 }
702}