Variadic constructors for Rust structs with automatic From trait implementations.
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.
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.
- FromN traits:
From1,From2,From3for variadic conversion - from! macro: Unified construction interface dispatching to appropriate trait
- VariadicFrom derive: Automatic trait implementation for structs
- Tuple conversion:
From<(T1, ...)>implementations alongsideFromN - Blanket implementations: Allow
From1<(T1, T2)>to delegate toFrom2 - Compile-time validation: Error for >3 arguments
- >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
- Upstream: Depends on
variadic_from_metafor proc macro - Downstream: Used by structs needing flexible constructors
- Field count boundary: 1-3 fields only (0 or >3 generates no code)
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.
The framework is guided by these principles:
- Convention over Configuration: The
#[derive(VariadicFrom)]macro should automatically generate the most common and intuitiveFrom-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 whichFromNtrait 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 andFrom<(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.
- 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. FromNTraits: 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 thefrom!macro.VariadicFromTrait: A marker trait implemented via a derive macro (#[derive(VariadicFrom)]). Its presence on a struct signals that the derive macro should automatically implement the appropriateFromNandFrom<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 toDefault::default(),From1::from1,From2::from2, orFrom3::from3based 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).
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.
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; }
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
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.
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:
- The macro receives the Abstract Syntax Tree (AST) of the struct.
- It inspects the struct's body to determine if it has named or unnamed (tuple) fields.
- It counts the number of fields.
- It extracts the types and generics of the struct.
- Code Generation Logic:
- Generics Handling: All generated
implblocks must correctly propagate the struct's generic parameters, including lifetimes, types, consts, andwhereclauses. - If field count is 1:
- Generates
impl<...> From1<FieldType> for StructName<...> - Generates
impl<...> From<FieldType> for StructName<...>which delegates toFrom1::from1. - Example for
struct S(i32):impl From<i32> for S { fn from(val: i32) -> Self { Self::from1(val) } }
- Generates
- If field count is 2:
- Generates
impl<...> From2<T1, T2> for StructName<...> - Generates
impl<...> From<(T1, T2)> for StructName<...>which delegates toFrom2::from2. - Convenience
From1: Generatesimpl<...> From1<T1> for StructName<...>if and only if the types of both fields (T1andT2) 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 } } }
- Generates
- If field count is 3:
- Generates
impl<...> From3<T1, T2, T3> for StructName<...> - Generates
impl<...> From<(T1, T2, T3)> for StructName<...>which delegates toFrom3::from3. - Convenience
From1andFrom2:- 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 assignsarg1to the first field andarg2to the second and third fields.
- Generates
- Generates
- If field count is 0 or greater than 3: The derive macro generates no code.
- Generics Handling: All generated
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 theDefaulttrait.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)whereN > 3results in acompile_error!, providing a clear message that the maximum number of arguments has been exceeded.
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 }
```
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)
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, explicitcompile_error!. - Unsupported Struct Size: The
VariadicFromderive 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-existentFromNimplementation. - Type Mismatches: Standard Rust type-checking rules apply. If the arguments passed to
from!do not match the types expected by the correspondingFromNimplementation, a compile error will occur.
The framework is designed to be extensible through manual trait implementation.
- Custom Logic: Developers can implement any of the
FromNtraits 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 thefrom!macro for more than 3 arguments.
- Modular Design with Traits: The crate's functionality is built upon a set of public
FromNtraits. 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 usescompile_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 thevariadic_fromcrate is imported. - The
VariadicFromderive 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'sCargo.toml.
- The
- Dependency Management: The
variadic_from_metacrate must prefer using themacro_toolscrate over direct dependencies onsyn,quote, orproc-macro2to leverage its higher-level abstractions. - Test Organization: All automated tests must reside in the
tests/directory, separate from thesrc/directory, to maintain a clear distinction between production and test code.
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");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();This specification document must adhere to the following rules to ensure its clarity, consistency, and maintainability.
- Ubiquitous Language: All terms defined in the
Key Terminologysection 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.
- The
variadic_fromcrate, containing the public traits,from!macro, and blanket implementations. - The
variadic_from_metacrate, containing the#[derive(VariadicFrom)]procedural macro. specification.md: This document.spec_addendum.md: A template for developers to fill in implementation-specific details.
The following checks must be performed to verify that an implementation of the variadic_from crate conforms to this specification.
- Derive on 1-Field Struct:
- Action: Apply
#[derive(VariadicFrom)]to a struct with 1 field. - Expected: The code compiles.
impl From1andimpl From<T>are generated and work as expected.
- Action: Apply
- 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 From2andimpl From<(i32, String)>are generated. The convenienceimpl From1<i32>is not generated.
- Action: Apply
- 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 convenienceimpl From1<i32>andimpl From2<i32, i32>are generated.
- Action: Apply
from!Macro Correctness:- Action: Call
from!(),from!(a),from!(a, b), andfrom!(a, b, c)on conforming types. - Expected: All calls compile and produce the correct struct instances.
- Action: Call
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.
- Action: Call
- Tuple Conversion Correctness:
- Action: Use
(a, b).into()andMyStruct::from((a, b))on a derived 2-field struct. - Expected: Both conversions compile and produce the correct struct instance.
- Action: Use
- Derive on 4-Field Struct:
- Action: Apply
#[derive(VariadicFrom)]to a struct with 4 fields and attempt to callfrom!(a, b). - Expected: The code fails to compile with an error indicating that
From2is not implemented, confirming the derive macro generated no code.
- Action: Apply
- Manual
From1Implementation:- Action: Create a struct with
#[derive(VariadicFrom)]and also provide a manualimpl From1<T> for MyStruct. - Expected: Calling
from!(t)uses the manual implementation, demonstrating that the compiler selects the more specific, user-defined logic.
- Action: Create a struct with
- Generics Handling:
- Action: Apply
#[derive(VariadicFrom)]to a struct with generic parameters and awhereclause. - Expected: The generated
implblocks correctly include the generics andwhereclause, and the code compiles.
- Action: Apply