refactor: remove cycle checking becuase API prevents cycles
This commit is contained in:
11 files changed
+543
-75
No files matched your search
@@ -0,0 +1,82 @@
|
||||
//! # Compile-Time Cycle Prevention
|
||||
//!
|
||||
//! This module documents how the type system prevents cycles in the DAG.
|
||||
//! **Cycles are impossible via the public API** due to the type-state pattern.
|
||||
//!
|
||||
//! ## Why Cycles Can't Happen
|
||||
//!
|
||||
//! 1. [`crate::builder::TaskBuilder::depends_on`] **consumes** the builder (takes `self` by value)
|
||||
//! 2. It returns a [`crate::types::TaskHandle`] - an immutable, opaque reference
|
||||
//! 3. [`crate::types::TaskHandle`] has **no methods** to add or modify dependencies
|
||||
//! 4. You can only wire dependencies **once** per task
|
||||
//!
|
||||
//! ## The Catch-22
|
||||
//!
|
||||
//! To create a cycle A→B→A, you would need:
|
||||
//! - Task A to depend on Task B's handle (requires B finalized)
|
||||
//! - Task B to depend on Task A's handle (requires A finalized)
|
||||
//!
|
||||
//! But you can't finalize both before wiring them together!
|
||||
//!
|
||||
//! ## Proof by Compilation Failure
|
||||
//!
|
||||
//! The following examples demonstrate that cycles fail to compile:
|
||||
//!
|
||||
//! ### Proof 1: Self-Loop Prevention via Move Semantics
|
||||
//!
|
||||
//! ```compile_fail
|
||||
//! use dagx::*;
|
||||
//!
|
||||
//! struct TaskA;
|
||||
//! #[dagx::task]
|
||||
//! impl TaskA {
|
||||
//! async fn run(&self, input: &i32) -> i32 {
|
||||
//! input + 1
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! async fn self_loop() {
|
||||
//! let dag = DagRunner::new();
|
||||
//! let a_builder = dag.add_task(TaskA);
|
||||
//!
|
||||
//! // ERROR: a_builder moved in the call to depends_on
|
||||
//! // Can't use 'a_builder' as both the builder and the dependency
|
||||
//! let a = a_builder.depends_on(&a_builder);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Proof 2: TaskHandle is Immutable
|
||||
//!
|
||||
//! ```compile_fail,E0599
|
||||
//! use dagx::*;
|
||||
//!
|
||||
//! struct NoInput;
|
||||
//! #[dagx::task]
|
||||
//! impl NoInput {
|
||||
//! async fn run(&self) -> i32 { 42 }
|
||||
//! }
|
||||
//!
|
||||
//! async fn unit_cycle() {
|
||||
//! let dag = DagRunner::new();
|
||||
//! let a_builder = dag.add_task(NoInput);
|
||||
//! let b_builder = dag.add_task(NoInput);
|
||||
//!
|
||||
//! // We can convert to handles (unit input = no dependencies required)
|
||||
//! let a_handle: TaskHandle<i32> = a_builder.into();
|
||||
//! let b_handle: TaskHandle<i32> = b_builder.into();
|
||||
//!
|
||||
//! // But TaskHandle has no methods to add dependencies!
|
||||
//! // ERROR: no method named `depends_on` found for type `TaskHandle<i32>`
|
||||
//! let _ = a_handle.depends_on(&b_handle);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Implications
|
||||
//!
|
||||
//! Because cycles are impossible at compile-time:
|
||||
//! - No runtime cycle detection is needed
|
||||
//! - Topological sort will always succeed
|
||||
//! - The DAG structure is guaranteed by the type system
|
||||
//!
|
||||
//! This is an example of **zero-cost abstraction** - the type system
|
||||
//! provides safety guarantees without runtime overhead.
|
||||
Reference in new issue
Block a user