Skip to main content

WorkflowDefinition

Struct WorkflowDefinition 

Source
pub struct WorkflowDefinition { /* private fields */ }
Expand description

One opaque, parsed app-kit.workflow/v2 definition.

Construct definitions through Self::parse; their internals cannot be copied through struct update syntax:

ⓘ
use app_kit::workflow::WorkflowDefinition;
fn reconstruct(definition: WorkflowDefinition) -> WorkflowDefinition {
WorkflowDefinition { ..definition }
}

Only the strict versioned language is accepted. Unknown/duplicate fields, invalid expressions, graph references and finite-bound violations fail before runtime work. Inspection returns passive metadata; composition runs the same compiler rather than exposing a mutable executable AST.

§Example

use app_kit::workflow::WorkflowDefinition;

let definition = WorkflowDefinition::parse(r#"{
    "schema":"app-kit.workflow/v2", "id":"counter", "revision":"r1",
    "state":{"count":0}, "required":["count"],
    "reducers":{"count":"reject_concurrent"}, "entry":"increment",
    "nodes":[{"id":"increment","step":{"kind":"state","mutations":[
        {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
    ]}}], "routes":{}
}"#)?;
assert_eq!(definition.graph().entry, "increment");

Implementations§

Source§

impl WorkflowDefinition

Source

pub fn graph(&self) -> GraphDescription

Describes authored nodes, ordered edges and declared state access.

This projection owns metadata only; it never retains executable bindings or lower handles. Callable metadata is resolved when bindings are supplied.

§Example
use app_kit::workflow::WorkflowDefinition;

let definition = WorkflowDefinition::parse(r#"{
    "schema":"app-kit.workflow/v2", "id":"counter", "revision":"r1",
    "state":{"count":0}, "required":["count"],
    "reducers":{"count":"reject_concurrent"}, "entry":"increment",
    "nodes":[{"id":"increment","step":{"kind":"state","mutations":[
        {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
    ]}}], "routes":{}
}"#)?;
let graph = definition.graph();
assert_eq!(graph.nodes[0].id, "increment");
assert_eq!(graph.nodes[0].writes, ["count"]);
Examples found in repository?
examples/graph.rs (line 43)
10fn main() -> Result<(), AppError> {
11    let mut imports = Vec::new();
12    for (id, prefix) in [("first-counter", "first_"), ("second-counter", "second_")] {
13        let definition = WorkflowDefinition::parse(
14            &json!({
15                "schema":"app-kit.workflow/v2", "id":id, "revision":"r1",
16                "state":{"count":0}, "required":["count"],
17                "reducers":{"count":"reject_concurrent"}, "entry":"add",
18                "nodes":[{"id":"add", "name":"Add one", "step":{
19                    "kind":"state", "mutations":[{
20                        "kind":"increment", "target":"count",
21                        "value":{"kind":"literal", "value":1}
22                    }]
23                }}],
24                "routes":{}
25            })
26            .to_string(),
27        )?;
28        imports.push(GraphImport {
29            definition,
30            prefix: prefix.into(),
31        });
32    }
33    let definition = WorkflowDefinition::compose(GraphComposition {
34        id: "two-counters".parse()?,
35        revision: "r1".into(),
36        entry: "first_add".into(),
37        imports,
38        connections: vec![GraphConnection {
39            from: "first_add".into(),
40            to: "second_add".into(),
41        }],
42    })?;
43    let graph = definition.graph();
44    assert_eq!(graph.entry, "first_add");
45    assert_eq!(graph.nodes.len(), 2);
46    assert_eq!(graph.nodes[0].id, "first_add");
47    assert_eq!(graph.nodes[1].id, "second_add");
48    assert!(
49        graph
50            .edges
51            .iter()
52            .any(|edge| { edge.from == "first_add" && edge.to.as_deref() == Some("second_add") })
53    );
54
55    let inspection = definition.export(GraphFormat::Json)?;
56    let inspection: serde_json::Value = serde_json::from_str(&inspection)
57        .map_err(|_| AppError::Integration("example graph JSON was invalid".into()))?;
58    assert_eq!(inspection["nodes"].as_array().map(Vec::len), Some(2));
59    assert!(definition.export(GraphFormat::Dot)?.starts_with("digraph "));
60    println!("{}", definition.export(GraphFormat::Mermaid)?);
61
62    // Built-in nodes retain their canonical authored source. Tracking requires
63    // actual source for every node and is a separate content-export format.
64    let tracking = graph.tracking_record()?;
65    assert_eq!(tracking["type"], "application");
66    assert_eq!(tracking["actions"].as_array().map(Vec::len), Some(2));
67    Ok(())
68}
Source

pub fn export(&self, format: GraphFormat) -> Result<String, AppError>

Renders deterministic inspection JSON, DOT or Mermaid without a subprocess.

Caller text is escaped for the chosen format. This is an inspection document, distinct from GraphDescription::tracking_record.

§Errors

Returns InvalidInput when the projection exceeds JSON or output-size limits, or Integration if graph metadata cannot be encoded.

§Example
use app_kit::workflow::WorkflowDefinition;

let definition = WorkflowDefinition::parse(r#"{
    "schema":"app-kit.workflow/v2", "id":"counter", "revision":"r1",
    "state":{"count":0}, "required":["count"],
    "reducers":{"count":"reject_concurrent"}, "entry":"increment",
    "nodes":[{"id":"increment","step":{"kind":"state","mutations":[
        {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
    ]}}], "routes":{}
}"#)?;
use app_kit::workflow::GraphFormat;
let mermaid = definition.export(GraphFormat::Mermaid)?;
assert!(mermaid.starts_with("flowchart TD"));
Examples found in repository?
examples/graph.rs (line 55)
10fn main() -> Result<(), AppError> {
11    let mut imports = Vec::new();
12    for (id, prefix) in [("first-counter", "first_"), ("second-counter", "second_")] {
13        let definition = WorkflowDefinition::parse(
14            &json!({
15                "schema":"app-kit.workflow/v2", "id":id, "revision":"r1",
16                "state":{"count":0}, "required":["count"],
17                "reducers":{"count":"reject_concurrent"}, "entry":"add",
18                "nodes":[{"id":"add", "name":"Add one", "step":{
19                    "kind":"state", "mutations":[{
20                        "kind":"increment", "target":"count",
21                        "value":{"kind":"literal", "value":1}
22                    }]
23                }}],
24                "routes":{}
25            })
26            .to_string(),
27        )?;
28        imports.push(GraphImport {
29            definition,
30            prefix: prefix.into(),
31        });
32    }
33    let definition = WorkflowDefinition::compose(GraphComposition {
34        id: "two-counters".parse()?,
35        revision: "r1".into(),
36        entry: "first_add".into(),
37        imports,
38        connections: vec![GraphConnection {
39            from: "first_add".into(),
40            to: "second_add".into(),
41        }],
42    })?;
43    let graph = definition.graph();
44    assert_eq!(graph.entry, "first_add");
45    assert_eq!(graph.nodes.len(), 2);
46    assert_eq!(graph.nodes[0].id, "first_add");
47    assert_eq!(graph.nodes[1].id, "second_add");
48    assert!(
49        graph
50            .edges
51            .iter()
52            .any(|edge| { edge.from == "first_add" && edge.to.as_deref() == Some("second_add") })
53    );
54
55    let inspection = definition.export(GraphFormat::Json)?;
56    let inspection: serde_json::Value = serde_json::from_str(&inspection)
57        .map_err(|_| AppError::Integration("example graph JSON was invalid".into()))?;
58    assert_eq!(inspection["nodes"].as_array().map(Vec::len), Some(2));
59    assert!(definition.export(GraphFormat::Dot)?.starts_with("digraph "));
60    println!("{}", definition.export(GraphFormat::Mermaid)?);
61
62    // Built-in nodes retain their canonical authored source. Tracking requires
63    // actual source for every node and is a separate content-export format.
64    let tracking = graph.tracking_record()?;
65    assert_eq!(tracking["type"], "application");
66    assert_eq!(tracking["actions"].as_array().map(Vec::len), Some(2));
67    Ok(())
68}
Source

pub fn compose(composition: GraphComposition) -> Result<Self, AppError>

Imports graph pieces with explicit node prefixes and appended connections.

The ordinary compiler checks the assembled definition. Conflicting state, reducers, reused revisions, node names, entrypoints and edges fail before any runtime or lower operation is admitted. Behavior binding keys retain their original meaning; only graph-local node references are prefixed.

§Errors

Returns InvalidInput for conflicting state/reducers, incompatible revisions, colliding prefixed nodes, invalid connections/entrypoints, graph errors, or exceeded compiler bounds. No runtime or lower operation is admitted.

§Example
use app_kit::workflow::WorkflowDefinition;
const SOURCE: &str = r#"{
    "schema":"app-kit.workflow/v2", "id":"counter", "revision":"r1",
    "state":{"count":0}, "required":["count"],
    "reducers":{"count":"reject_concurrent"}, "entry":"increment",
    "nodes":[{"id":"increment","step":{"kind":"state","mutations":[
        {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
    ]}}], "routes":{}
}"#;
let definition = WorkflowDefinition::parse(SOURCE)?;
use app_kit::workflow::{GraphComposition, GraphConnection, GraphImport};
let composition = GraphComposition {
    id: "composed".parse()?, revision: "r1".into(), entry: "a_increment".into(),
    imports: vec![
        GraphImport { definition, prefix: "a_".into() },
        GraphImport { definition: WorkflowDefinition::parse(SOURCE)?, prefix: "b_".into() },
    ],
    connections: vec![GraphConnection { from: "a_increment".into(), to: "b_increment".into() }],
};
let composed = WorkflowDefinition::compose(composition)?;
assert_eq!(composed.graph().nodes.len(), 2);
assert_eq!(composed.graph().entry, "a_increment");
Examples found in repository?
examples/graph.rs (lines 33-42)
10fn main() -> Result<(), AppError> {
11    let mut imports = Vec::new();
12    for (id, prefix) in [("first-counter", "first_"), ("second-counter", "second_")] {
13        let definition = WorkflowDefinition::parse(
14            &json!({
15                "schema":"app-kit.workflow/v2", "id":id, "revision":"r1",
16                "state":{"count":0}, "required":["count"],
17                "reducers":{"count":"reject_concurrent"}, "entry":"add",
18                "nodes":[{"id":"add", "name":"Add one", "step":{
19                    "kind":"state", "mutations":[{
20                        "kind":"increment", "target":"count",
21                        "value":{"kind":"literal", "value":1}
22                    }]
23                }}],
24                "routes":{}
25            })
26            .to_string(),
27        )?;
28        imports.push(GraphImport {
29            definition,
30            prefix: prefix.into(),
31        });
32    }
33    let definition = WorkflowDefinition::compose(GraphComposition {
34        id: "two-counters".parse()?,
35        revision: "r1".into(),
36        entry: "first_add".into(),
37        imports,
38        connections: vec![GraphConnection {
39            from: "first_add".into(),
40            to: "second_add".into(),
41        }],
42    })?;
43    let graph = definition.graph();
44    assert_eq!(graph.entry, "first_add");
45    assert_eq!(graph.nodes.len(), 2);
46    assert_eq!(graph.nodes[0].id, "first_add");
47    assert_eq!(graph.nodes[1].id, "second_add");
48    assert!(
49        graph
50            .edges
51            .iter()
52            .any(|edge| { edge.from == "first_add" && edge.to.as_deref() == Some("second_add") })
53    );
54
55    let inspection = definition.export(GraphFormat::Json)?;
56    let inspection: serde_json::Value = serde_json::from_str(&inspection)
57        .map_err(|_| AppError::Integration("example graph JSON was invalid".into()))?;
58    assert_eq!(inspection["nodes"].as_array().map(Vec::len), Some(2));
59    assert!(definition.export(GraphFormat::Dot)?.starts_with("digraph "));
60    println!("{}", definition.export(GraphFormat::Mermaid)?);
61
62    // Built-in nodes retain their canonical authored source. Tracking requires
63    // actual source for every node and is a separate content-export format.
64    let tracking = graph.tracking_record()?;
65    assert_eq!(tracking["type"], "application");
66    assert_eq!(tracking["actions"].as_array().map(Vec::len), Some(2));
67    Ok(())
68}
Source§

impl WorkflowDefinition

Source

pub fn parse(source: &str) -> Result<Self, AppError>

Parses and validates one strict versioned workflow document.

§Errors

Returns AppError::InvalidInput for malformed JSON, unsupported schema, unknown or duplicate members, invalid identities or expressions, inconsistent state/reducers, graph errors, or exceeded source/collection/depth limits.

§Example
use app_kit::workflow::WorkflowDefinition;

let definition = WorkflowDefinition::parse(r#"{
    "schema":"app-kit.workflow/v2", "id":"counter", "revision":"r1",
    "state":{"count":0}, "required":["count"],
    "reducers":{"count":"reject_concurrent"}, "entry":"increment",
    "nodes":[{"id":"increment","step":{"kind":"state","mutations":[
        {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
    ]}}], "routes":{}
}"#)?;
assert_eq!(definition.graph().id.to_string(), "counter");
assert!(WorkflowDefinition::parse(r#"{"schema":"app-kit.workflow/v1"}"#).is_err());
Examples found in repository?
examples/history_and_recovery.rs (lines 32-52)
31    async fn open_app(&self) -> Result<App<StateStore, Agent>, AppError> {
32        let definition = WorkflowDefinition::parse(
33            &json!({
34                "schema":"app-kit.workflow/v2", "id":"history-counter", "revision":"r1",
35                "state":{"count":0}, "required":["count"],
36                "reducers":{"count":"reject_concurrent"}, "entry":"first",
37                "nodes":[
38                    {"id":"first", "step":{"kind":"state", "mutations":[{
39                        "kind":"increment", "target":"count",
40                        "value":{"kind":"literal", "value":1}
41                    }]}},
42                    {"id":"second", "step":{"kind":"state", "mutations":[{
43                        "kind":"increment", "target":"count",
44                        "value":{"kind":"literal", "value":1}
45                    }]}}
46                ],
47                "routes":{"first":{"mode":"first", "branches":[{
48                    "target":{"kind":"node", "id":"second"}
49                }]}}
50            })
51            .to_string(),
52        )?;
53        // Agent supplies the static type for an empty inventory. No Agent value
54        // is constructed, and these native State nodes need no lower service.
55        App::open(AppConfig::<_, Agent>::new(
56            self.store.clone(),
57            Vec::new(),
58            vec![definition],
59        ))
60        .await
61    }
More examples
Hide additional examples
examples/performance.rs (line 178)
136    fn definition(linear: bool, actions: u64) -> Result<(WorkflowDefinition, usize), AppError> {
137        let mut nodes = Vec::new();
138        let mut routes = Map::new();
139        for index in 0..if linear { actions } else { 1 } {
140            let node = if linear {
141                format!("n{index:04}")
142            } else {
143                "count".to_owned()
144            };
145            nodes.push(json!({"id":node,"deadline_ms":NODE_DEADLINE_MS,
146            "step":{"kind":"state","mutations":[
147                {"kind":"increment","target":"count","value":{"kind":"literal","value":1}}
148            ]}}));
149            if linear && index + 1 < actions {
150                routes.insert(
151                    node,
152                    json!({"mode":"first","branches":[
153                        {"target":{"kind":"node","id":format!("n{:04}",index + 1)}}
154                    ]}),
155                );
156            } else if !linear {
157                routes.insert(
158                    node,
159                    json!({"mode":"first","branches":[
160                        {"when":{"kind":"less_than","left":{"kind":"state","path":"count"},
161                            "right":{"kind":"literal","value":actions}},
162                            "target":{"kind":"node","id":"count"}},
163                        {"target":{"kind":"end"}}
164                    ]}),
165                );
166            }
167        }
168        let source = json!({
169            "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
170            "state":{"count":0},"required":["count"],"reducers":{"count":"reject_concurrent"},
171            "entry":if linear {"n0000"} else {"count"}, "nodes":nodes,"routes":routes
172        })
173        .to_string();
174        assert!(
175            source.len() <= 1_048_576,
176            "generated definition must fit the real source bound"
177        );
178        Ok((WorkflowDefinition::parse(&source)?, source.len()))
179    }
180
181    fn read_manifest(&self) -> Result<Value, Box<dyn Error + Send + Sync>> {
182        // Reopen must never silently create a replacement empty store. Reject
183        // links and missing directories before passing the caller-owned path to LanceDB.
184        for name in [".performance-root", "manifest.json"] {
185            if !fs::symlink_metadata(self.root.join(name))?
186                .file_type()
187                .is_file()
188            {
189                return Err(format!("performance root needs a regular {name} file").into());
190            }
191        }
192        let mut marker = String::new();
193        File::open(self.root.join(".performance-root"))?
194            .take(MANIFEST_SCHEMA.len() as u64 + 1)
195            .read_to_string(&mut marker)?;
196        if marker != MANIFEST_SCHEMA {
197            return Err("root is not owned by this performance consumer".into());
198        }
199        if !fs::symlink_metadata(self.root.join("state"))?
200            .file_type()
201            .is_dir()
202            || fs::read_dir(self.root.join("state"))?
203                .next()
204                .transpose()?
205                .is_none()
206        {
207            return Err("performance state directory is absent or empty".into());
208        }
209        let mut encoded = String::new();
210        File::open(self.root.join("manifest.json"))?
211            .take(16_385)
212            .read_to_string(&mut encoded)?;
213        if encoded.len() > 16_384 {
214            return Err("performance manifest exceeds its bounded size".into());
215        }
216        let manifest: Value = serde_json::from_str(&encoded)?;
217        if manifest["schema"] != MANIFEST_SCHEMA
218            || manifest["dataset"] != "history"
219            || manifest["cleanup_joined"] != true
220        {
221            return Err("unsupported performance manifest".into());
222        }
223        let actions = manifest["actions"]
224            .as_u64()
225            .ok_or("manifest has no action count")?;
226        let rows = manifest["retained_rows"]
227            .as_u64()
228            .ok_or("manifest has no retained row count")?;
229        if !matches!(rows, 1_000 | 10_000)
230            || actions != (rows - 4) / 3
231            || manifest["terminal_checkpoint"] != rows - 1
232            || manifest["settings"] != Self::settings(actions)
233        {
234            return Err("manifest workload or settings do not match this consumer".into());
235        }
236        Ok(manifest)
237    }
238
239    fn samples(mut samples: Vec<Duration>) -> Value {
240        samples.sort_unstable();
241        let length = samples.len();
242        if length == 0 {
243            return json!({"count":0,"p50_ns":null,"p95_ns":null,"max_ns":null});
244        }
245        json!({"count":length,
246            "p50_ns":samples[(length * 50).div_ceil(100) - 1].as_nanos(),
247            "p95_ns":samples[(length * 95).div_ceil(100) - 1].as_nanos(),
248            "max_ns":samples[length - 1].as_nanos(),
249            "percentile":"nearest rank: ceil(p * n), one-based"})
250    }
251
252    fn verify_snapshot(snapshot: &RunSnapshot, actions: u64) {
253        assert!(matches!(snapshot.status, RunStatus::Succeeded));
254        assert!(snapshot.interactions.is_empty());
255        assert_eq!(
256            snapshot.state.values(),
257            &Map::from_iter([("count".into(), json!(actions))])
258        );
259        assert_eq!(snapshot.last_action_ordinal, Some(actions - 1));
260        assert_eq!(snapshot.outcomes.len(), 1);
261        assert_eq!(snapshot.outcomes[0].ordinal, actions - 1);
262        assert!(snapshot.outcomes[0].result.is_empty());
263    }
264
265    async fn history(
266        &self,
267        run: &WorkflowRun<StateStore, Agent, WorkloadAction>,
268        actions: u64,
269        linear: bool,
270        terminal_sequence: u64,
271    ) -> Result<Value, AppError> {
272        let ceiling = actions * 16 + 16;
273        let mut cursor = None;
274        let mut prior_sequence = None;
275        let mut rows = 0_u64;
276        let mut observed_actions = 0_u64;
277        let mut initial = 0;
278        let mut terminal = 0;
279        let mut controls = 0;
280        let mut halts = 0;
281        let mut samples = Vec::new();
282        let started = Instant::now();
283        loop {
284            assert!(
285                samples.len() < ceiling.div_ceil(PAGE_SIZE as u64) as usize + 1,
286                "history page ceiling exhausted without cursor completion"
287            );
288            let page_started = Instant::now();
289            let page = run
290                .history(cursor, NonZeroUsize::new(PAGE_SIZE).unwrap())
291                .await?;
292            samples.push(page_started.elapsed());
293            assert!(page.checkpoints.len() <= PAGE_SIZE);
294            assert!(
295                !page.checkpoints.is_empty() || page.next.is_none(),
296                "history cursor made no progress"
297            );
298            for row in &page.checkpoints {
299                assert_eq!(&row.run, run.id());
300                assert!(prior_sequence.is_none_or(|prior| row.sequence > prior));
301                prior_sequence = Some(row.sequence);
302                rows += 1;
303                assert!(
304                    rows <= ceiling,
305                    "declared retained-row workload ceiling exceeded"
306                );
307                match row.kind {
308                    CheckpointKind::Initial => {
309                        initial += 1;
310                        assert_eq!(observed_actions, 0);
311                        assert_eq!(row.sequence, 0);
312                    }
313                    CheckpointKind::Action => {
314                        assert_eq!(row.outcomes.len(), 1);
315                        let outcome = &row.outcomes[0];
316                        assert_eq!(outcome.ordinal, observed_actions);
317                        assert_eq!(
318                            outcome.node,
319                            if linear {
320                                format!("n{observed_actions:04}")
321                            } else {
322                                "count".into()
323                            }
324                        );
325                        assert!(outcome.result.is_empty());
326                        observed_actions += 1;
327                        assert_eq!(row.action_ordinal, Some(observed_actions - 1));
328                    }
329                    CheckpointKind::Terminal => {
330                        terminal += 1;
331                        assert!(matches!(row.status, RunStatus::Succeeded));
332                    }
333                    CheckpointKind::Control => {
334                        assert!(row.outcomes.is_empty());
335                        controls += 1;
336                        if let RunStatus::Halted { node, result } = &row.status {
337                            assert!(!linear);
338                            assert_eq!(row.sequence, 4);
339                            assert_eq!(node, "count");
340                            assert_eq!(result, &Some(Map::new()));
341                            halts += 1;
342                        }
343                    }
344                    CheckpointKind::Failed { .. } => {
345                        panic!("workload history contains a failed action")
346                    }
347                }
348                assert_eq!(
349                    row.state.values(),
350                    &Map::from_iter([("count".into(), json!(observed_actions))])
351                );
352                if !linear && matches!(row.sequence, 4 | 5) {
353                    assert!(matches!(row.kind, CheckpointKind::Control));
354                    assert_eq!(observed_actions, 1);
355                    assert_eq!(row.action_ordinal, Some(0));
356                    assert_eq!(row.position.as_deref(), Some("count"));
357                    if row.sequence == 5 {
358                        assert!(matches!(row.status, RunStatus::Paused));
359                    }
360                }
361            }
362            cursor = page.next;
363            // Each complete Checkpoint/WorkflowState is released with this page.
364            if cursor.is_none() {
365                break;
366            }
367        }
368        assert_eq!(initial, 1);
369        assert_eq!(terminal, 1);
370        assert_eq!(observed_actions, actions);
371        assert_eq!(prior_sequence, Some(terminal_sequence));
372        if !linear {
373            assert_eq!(rows, actions * 3 + 4);
374            assert_eq!(terminal_sequence, rows - 1);
375            assert_eq!(controls, actions * 2 + 2);
376            assert_eq!(halts, 1);
377            assert_eq!(samples.len(), (rows as usize).div_ceil(PAGE_SIZE));
378        }
379        let elapsed = started.elapsed();
380        let first = samples.first().copied().unwrap_or_default();
381        let subsequent: Duration = samples.iter().skip(1).copied().sum();
382        Ok(
383            json!({"rows":rows,"action_rows":observed_actions,"pages":samples.len(),
384            "elapsed_ns":elapsed.as_nanos(),"first_page_ns":first.as_nanos(),
385            "subsequent_pages_total_ns":subsequent.as_nanos(),
386            "rows_per_second":rows as f64 / elapsed.as_secs_f64(),"page_latency":Self::samples(samples)}),
387        )
388    }
389
390    async fn seed(
391        &self,
392        app: &App<StateStore, Agent, WorkloadAction>,
393        actions: u64,
394        linear: bool,
395    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
396        let workspace = app
397            .create_workspace(WorkspaceConfig {
398                name: Some("performance consumer".into()),
399                root: self.root.join("workspace"),
400            })
401            .await?;
402        let workflow = workspace.workflow("performance".parse()?).await?;
403        let mut run = workflow.prepare(WorkflowState::default()).await?;
404        let mut execution = run
405            .execution(ExecutionOptions {
406                halt_after: if linear {
407                    Vec::new()
408                } else {
409                    vec!["count".into()]
410                },
411                ..ExecutionOptions::default()
412            })
413            .await?;
414        let mut samples = Vec::with_capacity(actions as usize);
415        let started = Instant::now();
416        for ordinal in 0..actions {
417            let step_started = Instant::now();
418            let event = execution.step().await?;
419            samples.push(step_started.elapsed());
420            let Some(ExecutionEvent::Committed { outcomes, .. }) = event else {
421                panic!("each serial workload step must commit one action");
422            };
423            assert_eq!(outcomes.len(), 1);
424            assert_eq!(outcomes[0].ordinal, ordinal);
425            assert_eq!(
426                outcomes[0].node,
427                if linear {
428                    format!("n{ordinal:04}")
429                } else {
430                    "count".into()
431                }
432            );
433            assert!(outcomes[0].result.is_empty());
434            if !linear && ordinal == 0 {
435                // One public halt and explicit resume add two control rows to
436                // Initial + three checkpoints per action + Terminal.
437                let halted = execution.finish().await?;
438                assert!(matches!(&halted.status, RunStatus::Halted { node, result }
439                    if node == "count" && result == &Some(Map::new())));
440                assert_eq!(halted.checkpoint, 4);
441                assert_eq!(
442                    halted.state.values(),
443                    &Map::from_iter([("count".into(), json!(1))])
444                );
445                assert_eq!(halted.last_action_ordinal, Some(0));
446                assert!(halted.outcomes == outcomes);
447                assert!(halted.interactions.is_empty());
448                execution = run.execution(ExecutionOptions::default()).await?;
449            }
450        }
451        let snapshot = execution.finish().await?;
452        Self::verify_snapshot(&snapshot, actions);
453        let execution_elapsed = started.elapsed();
454        let history = self
455            .history(&run, actions, linear, snapshot.checkpoint)
456            .await?;
457        if let Command::SeedHistory { rows } = self.command {
458            assert_eq!(history["rows"], rows);
459        }
460        Ok(
461            json!({"dataset":if linear {"linear"} else {"history"},"actions":actions,
462            "workspace":workspace.id().to_string(),"run":run.id().to_string(),
463            "occurrence":snapshot.occurrence,
464            "expected_state":snapshot.state.values(),"last_action_ordinal":snapshot.last_action_ordinal,
465            "terminal_checkpoint":snapshot.checkpoint,"retained_rows":history["rows"],
466            "execution_ns":execution_elapsed.as_nanos(),
467            "actions_per_second":actions as f64 / execution_elapsed.as_secs_f64(),
468            "step_latency":Self::samples(samples),"history":history}),
469        )
470    }
471
472    async fn reopen(
473        &self,
474        app: &App<StateStore, Agent, WorkloadAction>,
475        manifest: &Value,
476        actions: u64,
477        open_only: bool,
478    ) -> Result<Value, Box<dyn Error + Send + Sync>> {
479        let started = Instant::now();
480        let workspace: WorkspaceId = manifest["workspace"]
481            .as_str()
482            .ok_or("missing workspace ID")?
483            .parse()?;
484        let run_id: RunId = manifest["run"].as_str().ok_or("missing run ID")?.parse()?;
485        let occurrence = manifest["occurrence"]
486            .as_str()
487            .ok_or("missing run occurrence")?
488            .to_owned();
489        let workspace = app.workspace(workspace).await?;
490        let workflow = workspace.workflow("performance".parse()?).await?;
491        let run = workflow
492            .open_run(RunSelector::Exact {
493                id: run_id,
494                occurrence: occurrence.clone(),
495            })
496            .await?;
497        let snapshot = run.snapshot().await?;
498        Self::verify_snapshot(&snapshot, actions);
499        assert_eq!(snapshot.occurrence, occurrence);
500        assert_eq!(json!(snapshot.checkpoint), manifest["terminal_checkpoint"]);
501        assert_eq!(json!(snapshot.state.values()), manifest["expected_state"]);
502        assert_eq!(
503            json!(snapshot.last_action_ordinal),
504            manifest["last_action_ordinal"]
505        );
506        let lookup = started.elapsed();
507        if open_only {
508            return Ok(
509                json!({"dataset":"history","actions":actions,"mode":"open-only",
510                "lookup_and_snapshot_ns":lookup.as_nanos(),
511                "seed_retained_rows":manifest["retained_rows"],"observed_history_rows":null}),
512            );
513        }
514        let history = self
515            .history(&run, actions, false, snapshot.checkpoint)
516            .await?;
517        assert_eq!(history["rows"], manifest["retained_rows"]);
518        Ok(
519            json!({"dataset":"history","actions":actions,"mode":"full-history",
520            "lookup_and_snapshot_ns":lookup.as_nanos(),"history":history}),
521        )
522    }
523
524    fn definitions(&self, actions: u64) -> Result<(Vec<WorkflowDefinition>, usize), AppError> {
525        if matches!(
526            self.command,
527            Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. }
528        ) {
529            let (definition, bytes) =
530                Self::definition(matches!(self.command, Command::Linear), actions)?;
531            return Ok((vec![definition], bytes));
532        }
533        let leaf = json!({
534            "schema":"app-kit.workflow/v2","id":"performance-leaf","revision":"r1",
535            "state":{"value":0,"result":0},"required":["value","result"],
536            "reducers":{"value":"reject_concurrent","result":"reject_concurrent"},
537            "entry":"work","nodes":[{"id":"work","deadline_ms":NODE_DEADLINE_MS,
538                "step":{"kind":"action","action":"performance-action","revision":"r1"}}],"routes":{}
539        });
540        let documents = match self.command {
541            Command::Ready => {
542                let mut nodes = vec![json!({"id":"fork","step":{"kind":"state","mutations":[]}})];
543                let mut branches = Vec::new();
544                for index in 0..32 {
545                    let node = format!("n{index:02}");
546                    nodes.push(json!({"id":node,"deadline_ms":NODE_DEADLINE_MS,
547                        "step":{"kind":"action","action":"performance-action","revision":"r1"}}));
548                    branches.push(json!({"target":{"kind":"node","id":node}}));
549                }
550                vec![json!({
551                    "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
552                    "state":{"value":0,"result":0},"required":["value","result"],
553                    "reducers":{"value":"reject_concurrent","result":"collect"},
554                    "entry":"fork","nodes":nodes,
555                    "routes":{"fork":{"mode":"broadcast","branches":branches}}
556                })]
557            }
558            Command::Map | Command::NestedCancel => {
559                let nested = matches!(self.command, Command::NestedCancel);
560                let items = if nested { 2 } else { 4_096 };
561                let mut documents = vec![
562                    json!({
563                        "schema":"app-kit.workflow/v2","id":"performance","revision":"r1",
564                        "state":{"value":0,"result":0},"required":["value","result"],
565                        "reducers":{"value":"reject_concurrent","result":"sum_integer"},
566                        "entry":"map","nodes":[{"id":"map","deadline_ms":if nested {NODE_DEADLINE_MS} else {WORKLOAD_BUDGET.as_millis() as u64},
567                            "step":{"kind":"map","workflow":if nested {"performance-mid"} else {"performance-leaf"},
568                            "items":{"kind":"literal","value":(0..items).collect::<Vec<_>>()},
569                            "input_key":"value","max_parallel":if nested {2} else {8},
570                            "reductions":[{"source":"result","target":"result","reducer":"sum_integer"}]}}],"routes":{}
571                    }),
572                    leaf,
573                ];
574                if nested {
575                    documents.push(json!({
576                        "schema":"app-kit.workflow/v2","id":"performance-mid","revision":"r1",
577                        "state":{"value":0,"result":0},"required":["value","result"],
578                        "reducers":{"value":"reject_concurrent","result":"sum_integer"},
579                        "entry":"map","nodes":[{"id":"map","deadline_ms":NODE_DEADLINE_MS,
580                            "step":{"kind":"map","workflow":"performance-leaf",
581                            "items":{"kind":"literal","value":(0..8).collect::<Vec<_>>()},
582                            "input_key":"value","max_parallel":8,
583                            "reductions":[{"source":"result","target":"result","reducer":"sum_integer"}]}}],"routes":{}
584                    }));
585                }
586                documents
587            }
588            Command::Repeated | Command::DelayedObserver => vec![leaf],
589            Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => {
590                unreachable!()
591            }
592        };
593        let mut bytes = 0;
594        let mut definitions = Vec::with_capacity(documents.len());
595        for document in documents {
596            let source = document.to_string();
597            assert!(source.len() <= 1_048_576);
598            bytes += source.len();
599            definitions.push(WorkflowDefinition::parse(&source)?);
600        }
601        Ok((definitions, bytes))
602    }
examples/graph.rs (lines 13-27)
10fn main() -> Result<(), AppError> {
11    let mut imports = Vec::new();
12    for (id, prefix) in [("first-counter", "first_"), ("second-counter", "second_")] {
13        let definition = WorkflowDefinition::parse(
14            &json!({
15                "schema":"app-kit.workflow/v2", "id":id, "revision":"r1",
16                "state":{"count":0}, "required":["count"],
17                "reducers":{"count":"reject_concurrent"}, "entry":"add",
18                "nodes":[{"id":"add", "name":"Add one", "step":{
19                    "kind":"state", "mutations":[{
20                        "kind":"increment", "target":"count",
21                        "value":{"kind":"literal", "value":1}
22                    }]
23                }}],
24                "routes":{}
25            })
26            .to_string(),
27        )?;
28        imports.push(GraphImport {
29            definition,
30            prefix: prefix.into(),
31        });
32    }
33    let definition = WorkflowDefinition::compose(GraphComposition {
34        id: "two-counters".parse()?,
35        revision: "r1".into(),
36        entry: "first_add".into(),
37        imports,
38        connections: vec![GraphConnection {
39            from: "first_add".into(),
40            to: "second_add".into(),
41        }],
42    })?;
43    let graph = definition.graph();
44    assert_eq!(graph.entry, "first_add");
45    assert_eq!(graph.nodes.len(), 2);
46    assert_eq!(graph.nodes[0].id, "first_add");
47    assert_eq!(graph.nodes[1].id, "second_add");
48    assert!(
49        graph
50            .edges
51            .iter()
52            .any(|edge| { edge.from == "first_add" && edge.to.as_deref() == Some("second_add") })
53    );
54
55    let inspection = definition.export(GraphFormat::Json)?;
56    let inspection: serde_json::Value = serde_json::from_str(&inspection)
57        .map_err(|_| AppError::Integration("example graph JSON was invalid".into()))?;
58    assert_eq!(inspection["nodes"].as_array().map(Vec::len), Some(2));
59    assert!(definition.export(GraphFormat::Dot)?.starts_with("digraph "));
60    println!("{}", definition.export(GraphFormat::Mermaid)?);
61
62    // Built-in nodes retain their canonical authored source. Tracking requires
63    // actual source for every node and is a separate content-export format.
64    let tracking = graph.tracking_record()?;
65    assert_eq!(tracking["type"], "application");
66    assert_eq!(tracking["actions"].as_array().map(Vec::len), Some(2));
67    Ok(())
68}
examples/actions_and_inputs.rs (lines 24-35)
23    async fn run() -> Result<(), Box<dyn Error + Send + Sync>> {
24        let definition = WorkflowDefinition::parse(
25            &json!({
26                "schema":"app-kit.workflow/v2", "id":"invoice", "revision":"r1",
27                "state":{"unit_price":7,"total":0}, "required":["unit_price","total"],
28                "reducers":{"unit_price":"reject_concurrent","total":"reject_concurrent"},
29                "entry":"calculate",
30                "nodes":[{"id":"calculate","step":{
31                    "kind":"action","action":"invoice","revision":"r1","project_result":false
32                }}], "routes":{}
33            })
34            .to_string(),
35        )?;
36        let store =
37            StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
38        // Native caller actions need no Agent value or provider service.
39        let mut app = App::open(
40            AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition])
41                .with_actions(vec![InvoiceAction::new()]),
42        )
43        .await?;
44        let observed = tokio::time::timeout(Duration::from_secs(15), async {
45            let workspace = app
46                .create_workspace(WorkspaceConfig {
47                    name: Some("invoice example".into()),
48                    root: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
49                })
50                .await?;
51            let workflow = workspace.workflow("invoice".parse()?).await?;
52            let mut run = workflow.prepare(WorkflowState::default()).await?;
53            let mut execution = run
54                .execution(ExecutionOptions {
55                    inputs: Map::from_iter([
56                        ("quantity".into(), json!(6)),
57                        ("trace".into(), json!("sample")),
58                    ]),
59                    ..ExecutionOptions::default()
60                })
61                .await?;
62            let Some(ExecutionEvent::Partial { result, .. }) = execution.next().await? else {
63                return Err(AppError::Integration(
64                    "expected the real action partial".into(),
65                ));
66            };
67            if result.get("progress") != Some(&json!("calculated")) {
68                return Err(AppError::Integration("partial changed".into()));
69            }
70            let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
71                return Err(AppError::Integration(
72                    "expected one committed boundary".into(),
73                ));
74            };
75            let expected =
76                json!({"quantity":6,"label":"order","currency":"USD","trace":"sample","total":42});
77            if outcomes.len() != 1 || Value::Object(outcomes[0].result.clone()) != expected {
78                return Err(AppError::Integration(
79                    "resolved inputs or action result changed".into(),
80                ));
81            }
82            let terminal = execution.finish().await?;
83            if !matches!(terminal.status, RunStatus::Succeeded)
84                || terminal.state.values()
85                    != json!({"unit_price":7,"total":42}).as_object().unwrap()
86            {
87                return Err(AppError::Integration(
88                    "only the explicit total write should persist".into(),
89                ));
90            }
91            println!(
92                "Partial: calculated; committed total: 42; transient inputs stayed out of state"
93            );
94            Ok::<(), AppError>(())
95        })
96        .await;
97        let closed = tokio::time::timeout(
98            Duration::from_secs(15),
99            app.shutdown(Shutdown::CancelAndDrain {
100                timeout: Duration::from_secs(10),
101            }),
102        )
103        .await;
104        drop(app);
105        observed.map_err(|_| AppError::Timeout("invoice execution timed out".into()))??;
106        closed.map_err(|_| AppError::Timeout("invoice cleanup timed out".into()))??;
107        Ok(())
108    }

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> ArchivePointee for T

Source§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
Source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> LayoutRaw for T

Source§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
Source§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

Source§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
Source§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
Source§

impl<T> Pipe for T
where T: ?Sized,

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
Source§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
Source§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Pointee for T

Source§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more