Crate component_model

Source
Expand description

Β§Module :: component_model

experimental rust-status docs.rs Open in Gitpod discord

Revolutionary type-safe component assignment for Rust. Build complex objects with zero boilerplate using derive macros and type-driven field setting. Perfect for configuration builders, fluent APIs, and object composition patterns.

Β§πŸš€ Why Component Model?

Traditional struct initialization is verbose and error-prone:

// Traditional approach - repetitive and fragile
let config = Config
{
  host : "localhost".to_string(),
  port : 8080,
};

// Builder pattern - lots of boilerplate
let config = ConfigBuilder::new()
.host( "localhost" )
.port( 8080 )
.build();

Component Model approach - Clean, type-safe, zero boilerplate:

use component_model::Assign;

#[ derive( Default, Assign ) ]
struct Config
{
  host : String,
  port : i32,
}

// Set components by type - no field names needed!
let mut config = Config::default();
config.assign( "localhost" );  // Automatically sets String field
config.assign( 8080 );         // Automatically sets i32 field  

// Or use fluent style
let config = Config::default()
.impute( "localhost" )
.impute( 8080 );

§✨ Key Features

  • 🎯 Type-driven assignment - Set fields by component type, not field name
  • πŸ”§ Zero boilerplate - Derive macros generate all implementations automatically
  • 🌊 Fluent APIs - Chainable impute() method for builder patterns
  • πŸ›‘οΈ Type safety - All assignments checked at compile time
  • πŸ”„ Flexible conversion - Accepts any type convertible to target field type
  • πŸ“¦ Multiple assignment - Set multiple components with ComponentsAssign
  • ⚑ Popular types support - Built-in support for Duration, PathBuf, SocketAddr, and more
  • πŸ—οΈ ComponentModel derive - Unified derive macro combining all functionality

Β§πŸš€ Quick Start

Add to your Cargo.toml:

[ dependencies ]
component_model = "0.4"

Β§Feature Flags

Component Model follows granular feature gating for minimal builds:

[ dependencies ]
# Minimal version - no features enabled by default  
component_model = { version = "0.4", default-features = false }

# Enable specific features as needed
component_model = { version = "0.4", features = [ "derive_component_model" ] }

# Or enable all features (default)
component_model = { version = "0.4", features = [ "full" ] }

Available features:

  • enabled - Master switch for core functionality
  • full - All features (enabled by default)
  • derive_component_model - Unified ComponentModel derive macro
  • derive_component_assign - Basic Assign derive macro
  • derive_components_assign - Multiple component assignment
  • derive_component_from - Component creation from single values
  • derive_from_components - Component creation from multiple values

Β§πŸ“– Core Concepts

Β§1. Basic Assignment with ComponentModel

use component_model::{ ComponentModel, Assign };

#[ derive( Default, Debug, ComponentModel ) ]
struct Person
{
  age : i32,
  name : String,
}

fn main()
{
  let mut person = Person::default();
  
  // Type-driven assignment - no field names!
  person.assign( 25 );           // Sets age : i32  
  person.assign( "Alice" );      // Sets name : String
  
  println!( "{:?}", person );    // Person { age: 25, name: "Alice" }
}

ComponentModel provides built-in support for popular Rust types with intelligent conversion:

use component_model::{ ComponentModel, Assign };
use std::time::Duration;
use std::path::PathBuf;

#[ derive( Default, Debug, ComponentModel ) ]
struct Config
{
  timeout : Duration,
  config_path : PathBuf,
  port : i32,
}

fn main()
{
  let mut config = Config::default();
  
  // Duration from seconds (u64)
  config.assign( 30u64 );  // Duration::from_secs( 30 )
  
  // Duration from fractional seconds (f64)  
  config.assign( 2.5f64 ); // Duration::from_secs_f64( 2.5 )
  
  // PathBuf from string slice
  config.assign( "/etc/app.conf" ); // PathBuf::from( "/etc/app.conf" )
  
  // i32 assignment
  config.assign( 8080i32 );
}

Β§3. Enum Fields in Structs

ComponentModel works with structs that contain enum fields, enabling type-safe enum assignment:

use component_model::{ ComponentModel, Assign };

#[ derive( Debug, PartialEq ) ]
enum Status
{
  Pending,
  Processing { progress : f64 },
  Completed { result : String },
  Failed { error : String },
}

impl Default for Status
{
  fn default() -> Self { Status::Pending }
}

#[ derive( Default, Debug, ComponentModel ) ]
struct Task
{
  id : u32,
  status : Status,
  priority : u8,
}

fn main()
{
  let mut task = Task::default();
  
  // Use field-specific methods with enums
  task.id_set( 42u32 );
  task.priority_set( 5u8 );
  task.status_set( Status::Processing { progress: 0.75 } );
  
  println!( "{:?}", task );
  
  // Fluent style with enums
  let completed_task = Task::default()
    .id_with( 100u32 )
    .status_with( Status::Completed { result: "Success".to_string() } )
    .priority_with( 1u8 );
    
  match completed_task.status {
    Status::Completed { result } => println!( "Task completed: {}", result ),
    _ => println!( "Unexpected status" ),
  }
}
Β§Complex Enum Fields
use component_model::{ ComponentModel, Assign };
use std::time::Duration;

#[ derive( Debug ) ]
enum ConnectionState
{
  Disconnected,
  Connecting { timeout : Duration },
  Connected { session_id : String },
}

impl Default for ConnectionState
{
  fn default() -> Self { ConnectionState::Disconnected }
}

#[ derive( Default, Debug, ComponentModel ) ]
struct NetworkService
{
  name : String,
  state : ConnectionState,
  retry_count : u32,
}

fn main()
{
  let mut service = NetworkService::default();
  
  // Field-specific methods work seamlessly with enum fields
  service.name_set( "WebSocket".to_string() );
  service.retry_count_set( 3u32 );
  service.state_set( ConnectionState::Connected { 
    session_id: "sess_12345".to_string() 
  } );
  
  // Fluent pattern with complex enums
  let connecting_service = NetworkService::default()
    .name_with( "HTTP Client".to_string() )
    .state_with( ConnectionState::Connecting { 
      timeout: Duration::from_secs( 30 )
    } )
    .retry_count_with( 0u32 );
    
  println!( "{:?}", connecting_service );
}

Note: Direct ComponentModel derive on enums is planned for future releases. Currently, enums work as field types in structs with ComponentModel.

Β§4. Fluent Builder Pattern

let person = Person::default()
.impute( "Bob" )           // Chainable assignment
.impute( 30 );             // Returns Self for chaining

Β§5. Multiple Component Assignment

use component_model::{ ComponentModel, Assign };

#[ derive( Default, ComponentModel ) ]
struct ServerConfig
{
  host : String,
  port : i32, 
}

let mut config = ServerConfig::default();
config.assign( "localhost" );    // String component
config.assign( 8080 );           // i32 component

Β§6. Manual Implementation (Advanced)

For custom behavior, implement traits manually:

use component_model::prelude::*;

struct Database
{
  url : String,
  pool_size : usize,
}

impl< T : Into< String > > Assign< String, T > for Database
{
  fn assign( &mut self, component : T )
  {
    self.url = component.into();
  }
}

impl< T : Into< usize > > Assign< usize, T > for Database
{  
  fn assign( &mut self, component : T )
  {
    self.pool_size = component.into();
  }
}

Β§πŸ“š Available Derive Macros

  • ComponentModel - ⭐ Recommended - Unified derive combining all functionality
  • Assign - Basic component assignment by type
  • ComponentsAssign - Multiple component assignment from tuples
  • ComponentFrom - Create objects from single components
  • FromComponents - Create objects from multiple components

§🎯 Real-World Use Cases

use component_model::{ ComponentModel, Assign };
use std::time::Duration;
use std::path::PathBuf;

#[ derive( Default, ComponentModel ) ]
struct DatabaseConfig
{
  host : String,
  port : i32,
  timeout : Duration,
}

let config = DatabaseConfig::default()
.impute( "postgres.example.com" )    // String
.impute( 5432 )                      // i32  
.impute( 30u64 );                    // Duration from seconds

Β§HTTP Client Builders

use component_model::{ ComponentModel, Assign };
use std::time::Duration;

#[ derive( Default, ComponentModel ) ]
struct HttpClient
{
  base_url : String,
  timeout : Duration,
}

let client = HttpClient::default()
.impute( "https://api.example.com" )
.impute( 30.0f64 );  // Duration from fractional seconds

Β§Game Entity Systems

use component_model::{ ComponentModel, Assign };

#[ derive( Default, ComponentModel ) ]
struct Player
{
  name : String,
  level : i32,
}

// Initialize components
let mut player = Player::default();
player.assign( "Hero" );
player.assign( 1 );

Β§πŸ§ͺ Examples

Explore the examples directory for comprehensive usage patterns:

ComponentModel includes built-in intelligent conversion for:

TypeInput TypesExample
Durationu64, f64, (u64, u32)config.assign( 30u64 )
PathBuf&str, Stringconfig.assign( "/path/file" )
SocketAddrComing soonString parsing planned
HashMapFramework readyVec conversion planned
HashSetFramework readyVec conversion planned

§⚠️ Important Limitations

Type Ambiguity: When a struct has multiple fields of the same type, assign() becomes ambiguous and won’t compile. This is by design for type safety.

struct Config
{
  host : String,
  database : String,  // Multiple String fields cause ambiguity
}

// This won't compile due to ambiguity:
// let mut config = Config::default();
// config.assign( "localhost" );  // Error: which String field?

Workarounds:

  1. Use different types when possible (e.g., String vs PathBuf)
  2. Use direct field assignment: config.host = "localhost".to_string();
  3. Implement manual Assign traits for specific use cases

Β§πŸ”— Learn More


Made with ❀️ as part of the wTools ecosystem

ModulesΒ§

dependency
Namespace with dependencies.
derive
Component model macro support
exposed
Exposed namespace of the module.
orphan
Parented namespace of the module.
own
Own namespace of the module.
popular_types
Popular type support for common Rust types. Popular type support for component model
prelude
Prelude to use essentials: use my_module::prelude::*.
std_types
Standard library type support

TraitsΒ§

Assign
Provides a generic interface for setting a component of a certain type on an object.
AssignWithType
The AssignWithType trait provides a mechanism to set a component on an object, utilizing the type information explicitly. This trait extends the functionality of Assign by allowing implementers to specify the component’s type at the method call site, enhancing expressiveness in code that manipulates object states.
OptionExt
Extension trait to provide a method for setting a component on an Option< Self > if the Option is currently None. If the Option is Some, the method will delegate to the Assign trait’s assign method.
PopularType
Marker trait to identify types that should get popular type support

Derive MacrosΒ§

Assign
Derives the Assign trait for struct fields, allowing each field to be set with a value that can be converted into the field’s type.
ComponentFrom
Macro to implement From for each component (field) of a structure. This macro simplifies the creation of From trait implementations for struct fields, enabling easy conversion from a struct reference to its field types.
ComponentModel
Unified derive macro that combines all component model functionality into a single annotation.
ComponentsAssign
Derives the ComponentsAssign trait for a struct, enabling components_assign which set all fields at once.
FromComponents
A procedural macro to automatically derive the From<T> trait implementation for a struct, enabling instances of one type to be converted from instances of another type.