2 releases

new 0.1.1 May 2, 2025
0.1.0 May 2, 2025

#327 in Procedural macros

Download history 242/week @ 2025-04-30

242 downloads per month

MIT license

23KB
283 lines

autoargs

A Rust procedural macro for generating argument structs with default values, allowing for named arguments and partial argument specification. Works with both standalone functions and struct methods.

Overview

When you annotate a function or method with #[autoargs], the macro:

  1. Generates a struct to hold the function's arguments
  2. Implements Default for that struct, using specified default expressions
  3. Creates a macro that lets you call the function with named arguments

Examples

Functions

use autoargs::{autoargs, default};

struct A(String);
struct B(u32);
struct C(bool);

fn foo() -> A { A("default_a".to_string()) }
fn bar() -> B { B(42) }
fn baz() -> C { C(true) }

#[autoargs]
fn draw(
    #[default = "foo()"]
    a: A,
    #[default = "bar()"]
    b: B, 
    #[default = "baz()"]
    c: C,
) -> String {
    format!("Drawing: a={}, b={}, c={}", a.0, b.0, c.0)
}

// Call with named arguments (unspecified arguments use defaults)
let result = draw!(
    a = A("custom_a".to_string()),
    b = B(100),
    // c is set to its default
);

Methods

For methods, you need to use both autoargs on the methods and impl_autoargs on the impl block:

use autoargs::{autoargs, impl_autoargs, default};

struct Canvas {
    width: u32,
    height: u32,
}

impl Canvas {
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }
}

#[impl_autoargs]
impl Canvas {
    #[autoargs]
    fn draw_rectangle(
        &self,
        #[default = "0"]
        x: u32,
        #[default = "0"]
        y: u32,
        #[default = "100"]
        width: u32,
        #[default = "50"]
        height: u32,
        #[default = "\"blue\".to_string()"]
        color: String,
    ) -> String {
        format!(
            "Drawing a {} rectangle at ({}, {}) with size {}x{} on canvas ({}x{})",
            color, x, y, width, height, self.width, self.height
        )
    }
    
    #[autoargs]
    fn resize(
        &mut self,
        #[default = "800"]
        width: u32,
        #[default = "600"]
        height: u32,
    ) -> String {
        self.width = width;
        self.height = height;
        format!("Resized canvas to {}x{}", width, height)
    }
}

let canvas = Canvas::new(800, 600);

// All defaults
let result1 = draw_rectangle!(&canvas);

// Some custom values
let result2 = draw_rectangle!(&canvas, x = 10, y = 20, color = "red".to_string());

// Create and pass an args struct
let args = DrawRectangleArgs {
    x: 30,
    y: 40,
    width: 200,
    height: 100,
    color: "green".to_string(),
};
let result3 = draw_rectangle!(&canvas, args);

Generated Code

For functions, the macro generates:

struct DrawArgs {
    pub a: A,
    pub b: B,
    pub c: C,
}

impl Default for DrawArgs {
    fn default() -> Self {
        Self {
            a: foo(),
            b: bar(),
            c: baz(),
        }
    }
}

fn draw(args: DrawArgs) -> String {
    let (a, b, c) = (args.a, args.b, args.c);
    // original function body
}

macro_rules! draw {
    () => {
        draw(DrawArgs::default())
    };
    ($($name:ident = $value:expr),* $(,)?) => {
        {
            let mut args = DrawArgs::default();
            $(
                args.$name = $value;
            )*
            draw(args)
        }
    };
    ($args:expr) => {
        draw($args)
    };
}

For methods, the autoargs attribute generates the args struct and modifies the method itself, while the impl_autoargs attribute creates the macro outside the impl block (since Rust doesn't allow macro definitions inside impl blocks):

// Generated by autoargs
struct DrawRectangleArgs {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
    pub color: String,
}

impl Default for DrawRectangleArgs {
    fn default() -> Self {
        Self {
            x: 0,
            y: 0, 
            width: 100,
            height: 50,
            color: "blue".to_string(),
        }
    }
}

// Method implementation is modified by autoargs
fn draw_rectangle(&self, args: DrawRectangleArgs) -> String {
    let (x, y, width, height, color) = (args.x, args.y, args.width, args.height, args.color);
    // original method body
}

// Generated by impl_autoargs
macro_rules! draw_rectangle {
    ($self:expr) => {
        $self.draw_rectangle(DrawRectangleArgs::default())
    };
    ($self:expr, $($name:ident = $value:expr),* $(,)?) => {
        {
            let mut args = DrawRectangleArgs::default();
            $(
                args.$name = $value;
            )*
            $self.draw_rectangle(args)
        }
    };
    ($self:expr, $args:expr) => {
        $self.draw_rectangle($args)
    };
}

Features

  • Named arguments with Rust-like syntax
  • Default values for arguments via attributes
  • Skip any argument to use its default value
  • Works with functions and all method types (&self, &mut self, self)
  • Generates proper structs and macros with correct visibility
  • Proper CamelCase naming convention for generated structs

Advanced Usage

Creating Custom Arg Structs

You can create a custom args struct and pass it directly:

let custom_args = DrawArgs {
    a: custom_a,
    b: DrawArgs::default().b,  // Use default for b
    c: custom_c,
};

// Pass the args struct directly
let result = draw!(custom_args);

Using Default Trait

If a parameter doesn't have a #[default = "..."] attribute, it will use the type's Default implementation:

#[autoargs]
fn simple(
    // Uses String::default()
    name: String,
    // Uses Option::<i32>::default()
    value: Option<i32>,
) { /* ... */ }

Best Practices

  1. Always use specific types for your parameters that implement the required traits
  2. Provide meaningful default values for each parameter
  3. Break complex functions into smaller functions with clear argument sets
  4. Use descriptive parameter names

Implementation Notes

Due to Rust's limitations that prevent macro definitions within impl blocks, a two-part approach is used for methods:

  1. #[autoargs] attribute on the method: Generates the args struct and rewrites the method to take the args struct
  2. #[impl_autoargs] attribute on the impl block: Identifies all #[autoargs] methods and generates macros for them outside the impl block
  3. #[default = "..."] attribute uses the default attribute from this crate

This approach allows for a clean, ergonomic usage pattern while respecting Rust's constraints.

Important: When using the library, make sure to import all the necessary components:

// For functions
use autoargs::{autoargs, default};

// For methods in impl blocks
use autoargs::{autoargs, impl_autoargs, default};

Installation

Add to your Cargo.toml:

[dependencies]
autoargs = "0.1.0"

License

MIT

Disclaimer

This crate was 100% vibe coded after my friend went on a 15-minute rant about how Rust has no default args and how all the reasons are bikeshedding or ridiculous complaints about how default args are "code smell".

Dependencies

~205–640KB
~15K SLoC