Files
dagx/examples/custom_types.rs
T
swaits 10af81ade7 test: comprehensive test improvements and infrastructure
This commit includes multiple test-related improvements from porting
parallel branch work and fixing test failures:

- Add justfile for development workflows (dagx-10)
  - Quick dev loop (fmt + clippy + test)
  - Pre-commit checks, pre-push validation, pre-release checks
  - Coverage, mutation testing, benchmarks, examples
  - Security audits, Miri, and more

- Port type mismatch tests for DagRunner::get() (dagx-13)
  - Test i32 to Vec<u8> mismatch
  - Test custom struct to HashMap mismatch
  - Test Vec<i32> to Vec<String> mismatch
  - Test Option<i32> to Option<String> mismatch

- Port TaskBuilder conversion tests (dagx-14)
  - Test conversion with multiple tasks preserves IDs
  - Test conversion with stateful tasks
  - Test conversion with complex output types (Vec, Option)

- Add custom_types.rs example (dagx-15)
  - Demonstrates custom User type with fan-out pattern
  - Shows Arc wrapping is transparent to users
  - Comprehensive documentation

- Fix all failing tests (dagx-16)
  - Remove incompatible task_fn tests from task/tests.rs
    (task_fn requires Arc-wrapped types, tests used raw types)
  - Fix listener type downcasts in node/tests.rs
    (Listener<Arc<i32>> not Listener<i32>)
  - Add #[allow(dead_code)] to unused test structs

- Cleanup unnecessary #[allow(dead_code)] in deps/tests.rs (dagx-9)

All 60 tests now pass.
2025-10-14 10:12:07 -06:00

120 lines
3.2 KiB
Rust

//! # Custom Types: Using Your Own Structs
//!
//! This example demonstrates that **ANY custom type** works in dagx as long as it implements
//! `Clone + Send + Sync + 'static`. You don't need to implement any special traits!
//!
//! ## What You'll Learn
//! - How to use custom structs as task outputs
//! - How custom types flow through the DAG automatically
//! - How one task's output can be consumed by multiple downstream tasks
//! - No trait implementations required (just derive Clone!)
//!
//! ## Key Insight
//!
//! **dagx works with ANY type automatically!** The `#[task]` macro generates type-specific
//! extraction logic, so you don't need to implement any special traits. Just make sure your
//! type implements:
//! - `Clone` (so it can be extracted from Arc)
//! - `Send + Sync` (required for async tasks)
//! - `'static` (no lifetime constraints)
//!
//! That's it! No special traits, no annotations, no boilerplate.
//!
//! ## Running This Example
//! ```bash
//! cargo run --example custom_types
//! ```
//!
//! ## Expected Output
//! ```text
//! Creating custom types example...
//!
//! Creating user: Alice (age: 30)
//! Processing user Alice for greeting
//! Processing user Alice for validation
//! Greeting: Hello, Alice!
//! Validation: Alice is valid (age 30 >= 18)
//! ```
use dagx::{task, DagRunner};
// Custom type - just derive Clone!
#[derive(Clone, Debug)]
struct User {
name: String,
age: u32,
}
// Task that produces a custom type
struct CreateUser {
name: String,
age: u32,
}
#[task]
impl CreateUser {
async fn run(&self) -> User {
println!("Creating user: {} (age: {})", self.name, self.age);
User {
name: self.name.clone(),
age: self.age,
}
}
}
// Task that consumes the custom type and produces a String
struct GreetUser;
#[task]
impl GreetUser {
async fn run(user: &User) -> String {
println!("Processing user {} for greeting", user.name);
format!("Hello, {}!", user.name)
}
}
// Another task that also consumes the same custom type
// This demonstrates fan-out: one output consumed by multiple tasks
struct ValidateUser;
#[task]
impl ValidateUser {
async fn run(user: &User) -> String {
println!("Processing user {} for validation", user.name);
if user.age >= 18 {
format!("{} is valid (age {} >= 18)", user.name, user.age)
} else {
format!("{} is too young (age {} < 18)", user.name, user.age)
}
}
}
#[tokio::main]
async fn main() {
println!("Creating custom types example...\n");
let dag = DagRunner::new();
// Create a task that produces a custom User type
let user_task = dag.add_task(CreateUser {
name: "Alice".to_string(),
age: 30,
});
// Two different tasks consume the same custom User type
// dagx automatically handles Arc wrapping, so both tasks get &User
let greeting = dag.add_task(GreetUser).depends_on(&user_task);
let validation = dag.add_task(ValidateUser).depends_on(&user_task);
// Execute the DAG
dag.run(|fut| {
tokio::spawn(fut);
})
.await
.unwrap();
// Print results
println!("Greeting: {}", dag.get(greeting).unwrap());
println!("Validation: {}", dag.get(validation).unwrap());
}