Skip to content

Latest commit

 

History

History
313 lines (247 loc) · 18.7 KB

File metadata and controls

313 lines (247 loc) · 18.7 KB

variadic_from

Variadic constructors for Rust structs with automatic From trait implementations.

Overview

The variadic_from crate provides a powerful procedural macro and helper traits to simplify the creation of flexible constructors for Rust structs. It automates the implementation of From-like traits, allowing structs to be instantiated from a variable number of arguments or tuples, reducing boilerplate and enhancing code readability.

Note: This specification governs the behavior of both the variadic_from crate, which provides the user-facing traits and macros, and the variadic_from_meta crate, which implements the procedural derive macro. Together, they form a single functional unit.

Scope

Responsibility

variadic_from is responsible for generating flexible constructors for structs with 1-3 fields. It provides the From1, From2, From3 traits, the from! macro for invocation, and the VariadicFrom derive macro for automatic implementation.

In-Scope

  • FromN traits: From1, From2, From3 for variadic conversion
  • from! macro: Unified construction interface dispatching to appropriate trait
  • VariadicFrom derive: Automatic trait implementation for structs
  • Tuple conversion: From<(T1, ...)> implementations alongside FromN
  • Blanket implementations: Allow From1<(T1, T2)> to delegate to From2
  • Compile-time validation: Error for >3 arguments

Out-of-Scope

  • >3 fields: No support for structs with more than 3 fields
  • Enums: Only structs are supported
  • Runtime dispatching: All conversions are compile-time
  • Generic field type coercion: No automatic type conversion

Boundaries

  • Upstream: Depends on variadic_from_meta for proc macro
  • Downstream: Used by structs needing flexible constructors
  • Field count boundary: 1-3 fields only (0 or >3 generates no code)

Technical Specification (v1.1)

1. Introduction & Core Concepts

1.1. Problem Solved

In Rust, creating struct instances often requires boilerplate, especially for structs with multiple fields or for those that need to be constructed from different sets of inputs. This crate aims to significantly reduce this boilerplate and improve developer ergonomics by providing a flexible, "variadic" constructor macro (from!). This allows for intuitive struct instantiation from a variable number of arguments, tuples, or single values, reducing cognitive load and making the code cleaner and more readable.

1.2. Goals & Philosophy

The framework is guided by these principles:

  • Convention over Configuration: The #[derive(VariadicFrom)] macro should automatically generate the most common and intuitive From-like implementations without requiring extra attributes or configuration. The structure of the type itself is the configuration.
  • Minimal Syntactic Noise: The user-facing from! macro provides a clean, concise, and unified interface for constructing objects, abstracting away the underlying implementation details of which FromN trait is being called.
  • Seamless Integration: The crate should feel like a natural extension of the Rust language. It achieves this by automatically implementing the standard From<T> trait for single fields and From<(T1, T2, ...)> for multiple fields, enabling idiomatic conversions using .into().
  • Non-Intrusive Extensibility: While the derive macro handles the common cases, the system is built on a foundation of public traits (From1, From2, From3) that developers can implement manually for custom behavior or to support types not covered by the macro.

1.3. Key Terminology (Ubiquitous Language)

  • Variadic Constructor: A constructor that can accept a variable number of arguments. In the context of this crate, this is achieved through the from! macro.
  • FromN Traits: A set of custom traits (From1, From2, From3) that define a contract for constructing a type from a specific number (N) of arguments. They are the low-level mechanism enabling the from! macro.
  • VariadicFrom Trait: A marker trait implemented via a derive macro (#[derive(VariadicFrom)]). Its presence on a struct signals that the derive macro should automatically implement the appropriate FromN and From<T>/From<tuple> traits based on the number of fields in the struct.
  • from! Macro: A declarative, user-facing macro that provides the primary interface for variadic construction. It resolves to a call to Default::default(), From1::from1, From2::from2, or From3::from3 based on the number of arguments provided.
  • Named Struct: A struct where fields are defined with explicit names, e.g., struct MyStruct { a: i32 }.
  • Unnamed Struct (Tuple Struct): A struct where fields are defined by their type only, e.g., struct MyStruct(i32).

1.4. Versioning Strategy

The variadic_from crate adheres to the Semantic Versioning 2.0.0 (SemVer) standard.

  • MAJOR version changes indicate incompatible API changes.
  • MINOR version changes introduce new, backward-compatible functionality (e.g., increasing the maximum number of supported arguments).
  • PATCH version changes are for backward-compatible bug fixes.

This specification document is versioned in lockstep with the crate itself.

2. Core Object Definitions

2.1. The FromN Traits

The FromN traits provide a standardized, type-safe interface for constructing a type from a specific number (N) of arguments. They form the low-level contract that the high-level from! macro and VariadicFrom derive macro use.

  • From1<Arg>
    pub trait From1<Arg>
    where
        Self: Sized,
    {
        fn from1(arg: Arg) -> Self;
    }
  • From2<Arg1, Arg2>
    pub trait From2<Arg1, Arg2>
    where
        Self: Sized,
    {
        fn from2(arg1: Arg1, arg2: Arg2) -> Self;
    }
  • From3<Arg1, Arg2, Arg3>
    pub trait From3<Arg1, Arg2, Arg3>
    where
        Self: Sized,
    {
        fn from3(arg1: Arg1, arg2: Arg2, arg3: Arg3) -> Self;
    }

2.2. Blanket Implementations

To improve ergonomics, the framework provides blanket implementations that allow From1 to be the single entry point for tuple-based conversions. This enables from!((a, b)) to work seamlessly.

  • impl<T, All> From1<(T,)> for All where All: From1<T>
  • impl<T1, T2, All> From1<(T1, T2)> for All where All: From2<T1, T2>
  • impl<T1, T2, T3, All> From1<(T1, T2, T3)> for All where All: From3<T1, T2, T3>
  • impl<All> From1<()> for All where All: Default

2.3. The VariadicFrom Trait

This is a marker trait that enables the #[derive(VariadicFrom)] macro. It contains no methods. Its sole purpose is to be attached to a struct to signal that the derive macro should perform code generation for it.

3. Processing & Execution Model

3.1. The VariadicFrom Derive Macro (variadic_from_meta)

The derive macro is the core of the crate's code generation capabilities.

  • Activation: The macro is activated when a struct is annotated with #[derive(VariadicFrom)].
  • Processing Steps:
    1. The macro receives the Abstract Syntax Tree (AST) of the struct.
    2. It inspects the struct's body to determine if it has named or unnamed (tuple) fields.
    3. It counts the number of fields.
    4. It extracts the types and generics of the struct.
  • Code Generation Logic:
    • Generics Handling: All generated impl blocks must correctly propagate the struct's generic parameters, including lifetimes, types, consts, and where clauses.
    • If field count is 1:
      • Generates impl<...> From1<FieldType> for StructName<...>
      • Generates impl<...> From<FieldType> for StructName<...> which delegates to From1::from1.
      • Example for struct S(i32): impl From<i32> for S { fn from(val: i32) -> Self { Self::from1(val) } }
    • If field count is 2:
      • Generates impl<...> From2<T1, T2> for StructName<...>
      • Generates impl<...> From<(T1, T2)> for StructName<...> which delegates to From2::from2.
      • Convenience From1: Generates impl<...> From1<T1> for StructName<...> if and only if the types of both fields (T1 and T2) are identical. The implementation assigns the single argument to both fields.
      • Example for struct S { a: i32, b: i32 }: impl From1<i32> for S { fn from1(val: i32) -> Self { Self { a: val, b: val } } }
    • If field count is 3:
      • Generates impl<...> From3<T1, T2, T3> for StructName<...>
      • Generates impl<...> From<(T1, T2, T3)> for StructName<...> which delegates to From3::from3.
      • Convenience From1 and From2:
        • Generates impl<...> From1<T1> for StructName<...> if and only if all three field types (T1, T2, T3) are identical.
        • Generates impl<...> From2<T1, T2> for StructName<...> if and only if the second and third field types (T2, T3) are identical. The implementation assigns arg1 to the first field and arg2 to the second and third fields.
    • If field count is 0 or greater than 3: The derive macro generates no code.

3.2. The from! Macro (variadic_from)

The from! macro provides a convenient, unified syntax for variadic construction. It is a standard macro_rules! macro that dispatches to the correct implementation based on the number of arguments provided at the call site.

  • Resolution Rules:
    • from!() expands to ::core::default::Default::default(). This requires the target type to implement the Default trait.
    • from!(arg1) expands to $crate::variadic::From1::from1(arg1).
    • from!(arg1, arg2) expands to $crate::variadic::From2::from2(arg1, arg2).
    • from!(arg1, arg2, arg3) expands to $crate::variadic::From3::from3(arg1, arg2, arg3).
    • from!(arg1, ..., argN) where N > 3 results in a compile_error!, providing a clear message that the maximum number of arguments has been exceeded.

4. Interaction Modalities

4.1. Direct Instantiation via from!

This is the primary and most expressive way to use the crate.

  • Example:
    # use variadic_from::exposed::*;
    #[derive(Debug, PartialEq, Default, VariadicFrom)]
    struct Point 

{ x: i32, y: i32, }

// Zero arguments (requires `Default`)
let p0: Point = from!(); // Point { x: 0, y: 0 }

// One argument (uses generated convenience `From1`)
let p1: Point = from!(10); // Point { x: 10, y: 10 }

// Two arguments (uses generated `From2`)
let p2: Point = from!(10, 20); // Point { x: 10, y: 20 }
```

4.2. Standard Conversion via From and Into

By generating From<T> and From<tuple> implementations, the derive macro enables seamless integration with the standard library's conversion traits.

  • Example:
    # use variadic_from::exposed::*;
    #[derive(Debug, PartialEq, Default, VariadicFrom)]
    struct Point(i32, i32);
    
    // Using From::from
    let p1: Point = Point::from((10, 20)); // Point(10, 20)
    
    // Using .into()
    let p2: Point = (30, 40).into(); // Point(30, 40)
    
    // Using from! with a tuple (leverages the From1 blanket impl)
    let p3: Point = from!((50, 60)); // Point(50, 60)

5. Cross-Cutting Concerns

5.1. Error Handling Strategy

All error handling is designed to occur at compile time, providing immediate feedback to the developer.

  • Invalid Argument Count: Calling the from! macro with more than 3 arguments results in a clear, explicit compile_error!.
  • Unsupported Struct Size: The VariadicFrom derive macro will not generate code for structs with 0 or more than 3 fields. This will result in a standard "method not found" or "trait not implemented" compile error if code attempts to use a non-existent FromN implementation.
  • Type Mismatches: Standard Rust type-checking rules apply. If the arguments passed to from! do not match the types expected by the corresponding FromN implementation, a compile error will occur.

5.2. Extensibility Model

The framework is designed to be extensible through manual trait implementation.

  • Custom Logic: Developers can implement any of the FromN traits manually to provide custom construction logic that overrides the derived behavior or adds new conversion paths.
  • Supporting Larger Structs: For structs with more than 3 fields, developers can manually implement the standard From<tuple> trait to provide similar ergonomics, though they will not be able to use the from! macro for more than 3 arguments.

6. Architectural Principles & Design Rules

  • Modular Design with Traits: The crate's functionality is built upon a set of public FromN traits. This allows for clear contracts and enables developers to extend the functionality with their own custom implementations.
  • Private Implementation: Internal logic is kept in private modules (e.g., variadic). The public API is exposed through a controlled interface (exposed, prelude) to hide implementation details and allow for internal refactoring without breaking changes.
  • Compile-Time Safety: All error handling must occur at compile time. The from! macro uses compile_error! for invalid argument counts, and the derive macro relies on the compiler to report type mismatches or missing trait implementations.
  • Generated Path Resolution:
    • The from! declarative macro must use $crate::... paths (e.g., $crate::variadic::From1) to ensure it works correctly regardless of how the variadic_from crate is imported.
    • The VariadicFrom derive macro must use absolute paths (e.g., ::variadic_from::exposed::From1) to ensure the generated code is robust against crate renaming and aliasing in the consumer's Cargo.toml.
  • Dependency Management: The variadic_from_meta crate must prefer using the macro_tools crate over direct dependencies on syn, quote, or proc-macro2 to leverage its higher-level abstractions.
  • Test Organization: All automated tests must reside in the tests/ directory, separate from the src/ directory, to maintain a clear distinction between production and test code.

7. Appendices

A.1. Code Examples

Named Struct Example
use variadic_from::exposed::*;

#[derive(Debug, PartialEq, Default, VariadicFrom)]
struct UserProfile 
{
    id: u32,
    username: String,
}

// Manual implementation for a single argument for convenience
impl From1<&str> for UserProfile 
{
    fn from1(name: &str) -> Self 
{
        Self { id: 0, username: name.to_string() }
    }
}

// Generated implementations allow these conversions:
let _user1: UserProfile = from!(101, "admin".to_string());
let _user2: UserProfile = (102, "editor".to_string()).into();

// Manual implementation allows this:
let _user3: UserProfile = from!("guest");
Unnamed (Tuple) Struct Example
use variadic_from::exposed::*;

#[derive(Debug, PartialEq, Default, VariadicFrom)]
struct Point(i32, i32, i32);

// Generated implementations allow these conversions:
let _p1: Point = from!();
let _p2: Point = from!(1, 2, 3);
let _p3: Point = (4, 5, 6).into();

8. Meta-Requirements

This specification document must adhere to the following rules to ensure its clarity, consistency, and maintainability.

  • Ubiquitous Language: All terms defined in the Key Terminology section must be used consistently throughout this document and all related project artifacts.
  • Repository as Single Source of Truth: The version control repository is the single source of truth for all project artifacts, including this specification.
  • Naming Conventions: All asset names (files, variables, etc.) must use snake_case.
  • Mandatory Structure: This document must follow the agreed-upon section structure. Additions must be justified and placed appropriately.

9. Deliverables

  • The variadic_from crate, containing the public traits, from! macro, and blanket implementations.
  • The variadic_from_meta crate, containing the #[derive(VariadicFrom)] procedural macro.
  • specification.md: This document.
  • spec_addendum.md: A template for developers to fill in implementation-specific details.

10. Conformance Check Procedure

The following checks must be performed to verify that an implementation of the variadic_from crate conforms to this specification.

  1. Derive on 1-Field Struct:
    • Action: Apply #[derive(VariadicFrom)] to a struct with 1 field.
    • Expected: The code compiles. impl From1 and impl From<T> are generated and work as expected.
  2. Derive on 2-Field Named Struct:
    • Action: Apply #[derive(VariadicFrom)] to a named struct with 2 fields of different types (e.g., i32, String).
    • Expected: The code compiles. impl From2 and impl From<(i32, String)> are generated. The convenience impl From1<i32> is not generated.
  3. Derive on 3-Field Unnamed Struct:
    • Action: Apply #[derive(VariadicFrom)] to an unnamed (tuple) struct with 3 fields of the same type (e.g., i32, i32, i32).
    • Expected: The code compiles. impl From3, impl From<(i32, i32, i32)>, and convenience impl From1<i32> and impl From2<i32, i32> are generated.
  4. from! Macro Correctness:
    • Action: Call from!(), from!(a), from!(a, b), and from!(a, b, c) on conforming types.
    • Expected: All calls compile and produce the correct struct instances.
  5. from! Macro Error Handling:
    • Action: Call from!(a, b, c, d).
    • Expected: The code fails to compile with an error message explicitly stating the argument limit has been exceeded.
  6. Tuple Conversion Correctness:
    • Action: Use (a, b).into() and MyStruct::from((a, b)) on a derived 2-field struct.
    • Expected: Both conversions compile and produce the correct struct instance.
  7. Derive on 4-Field Struct:
    • Action: Apply #[derive(VariadicFrom)] to a struct with 4 fields and attempt to call from!(a, b).
    • Expected: The code fails to compile with an error indicating that From2 is not implemented, confirming the derive macro generated no code.
  8. Manual From1 Implementation:
    • Action: Create a struct with #[derive(VariadicFrom)] and also provide a manual impl From1<T> for MyStruct.
    • Expected: Calling from!(t) uses the manual implementation, demonstrating that the compiler selects the more specific, user-defined logic.
  9. Generics Handling:
    • Action: Apply #[derive(VariadicFrom)] to a struct with generic parameters and a where clause.
    • Expected: The generated impl blocks correctly include the generics and where clause, and the code compiles.