2 unstable releases

0.2.0 Oct 6, 2024
0.1.0 Sep 17, 2024

#198 in Procedural macros

MIT and AGPL-3.0-or-later

84KB
1.5K SLoC

defamed

Default, named and positional parameters.

Functions | Structs

crate docs build status

Quick start

Tag supported items with #[defamed::defamed], default parameters with #[def] and use the generated macro with any combination of positional and named parameters.

Functions

#[defamed::defamed]
fn complex_function(
    lhs: i32,
    rhs: i32,
    // literals can be used as default values
    #[def(true)] add: bool,
    // if no default value is provided, the type must implement Default
    #[def] divide_result_by: Option<i32>,
) -> i32 {
    let intermediate = if add { lhs + rhs } else { lhs - rhs };

    match divide_result_by {
        Some(div) => intermediate / div,
        None => intermediate,
    }
}

assert_eq!(10, complex_function!(5, 5));
assert_eq!(10, complex_function!(rhs = 5, lhs = 15, add = false));
assert_eq!(5, complex_function!(5, 5, divide_result_by = Some(2)));

Structs

Struct macros can be used in-place of the builder pattern.

/// A struct that does not fully implement core::default::Default
#[defamed::defamed]
#[derive(Debug, PartialEq, Default)]
struct PartialDefault<'a> {
    pub inner: &'a [u8],

    #[def]
    pub idx: usize,

    #[def]
    pub len: usize,
}

// use struct update syntax `..` to fill in all remaining fields
let pd = PartialDefault! {inner: &[1, 2, 3], idx: 1, ..};
let reference = PartialDefault { inner: &[1, 2, 3], idx: 1, len: 0 };

assert_eq!(reference, pd);

/// Tuple structs work very similarly to functions - but all parameters are positional
#[derive(Debug, PartialEq)]
#[defamed::defamed]
struct TupleStruct(i32, #[def] i32, #[def] bool);

let ts_a = TupleStruct!(5);
let ts_b = TupleStruct!(5, 0);
let reference = TupleStruct(5, 0, false);

assert_eq!(reference, ts_a);
assert_eq!(reference, ts_b);

Features

  • Named and positional parameters in any order à la Python
  • Generated macros live in the same path as the associated item
  • Export macros for use in other crates
  • With the heavy lifting done at compile time

Similar crates

named

  • locally scoped macros only

default_args

  • accepts python-like syntax in function signature
  • may cause code clutter due to using function macros

duang

  • similar to default_args

optargs

  • infers optional parameters that use the Option type
  • struct builder macro

default_kwargs

  • keyword argument passing only

nade

  • macro requires explicit import to call underlying function

Parameter passing

The macro accepts parameters in any permutation as long as the following conditions are met:

  • positional parameters order follows the original function signature
  • all positional parameters are passed first
  • named parameters come after all positional parameters
  • named parameters can be included in any order
  • default parameters are passed last
  • default parameters can be excluded
Example
/// Add/sub 2 numbers, then take the absolute value, if applicable
#[defamed::defamed]
fn pos_and_def(
    lhs: i32,
    rhs: i32,
    #[def(true)]
    add: bool,
    #[def]
    abs_val: bool
) -> i32 {
    let inter = if add {lhs + rhs} else {lhs - rhs};
    if abs_val {inter.abs()} else {inter}
}

// original fn
assert_eq!(20, pos_and_def(5, 15, true, false));

// all positional
assert_eq!(20, pos_and_def!(5, 15, true, false));
// all named
assert_eq!(20, pos_and_def!(lhs=5, rhs=15, add=true, abs_val=false));
// all named, in any order, defaults last
assert_eq!(20, pos_and_def!(rhs=15, lhs=5, abs_val=false, add=true));
// defaults excluded
assert_eq!(20, pos_and_def!(5, 15));
// defaults excluded, positional in any order
assert_eq!(20, pos_and_def!(rhs=15, lhs=5));
// some positional, some named
assert_eq!(20, pos_and_def!(5, rhs=15));

// overriding first default parameter as positional
assert_eq!(20, pos_and_def!(25, 5, false));
// overriding second default parameter as named
assert_eq!(20, pos_and_def!(5, -25, abs_val=true));

Macro scope

Macros generated by defamed can be exported and used by other crates if the path to the underlying function is public.

Private

For functions that are used in the same module as they are defined, the macro resolves the call directly.

#[defamed::defamed]
fn local_scope() {}
// macro resolves to:
local_scope!() => local_scope()

When private functions are used inside child modules, the module path relative to the crate root needs to be provided.

#[defamed::defamed(crate)]
fn top_level_local_scope() {}

mod inner_consumer {
    fn inner() {
        super::top_level_local_scope!()
    }
}

Public or Restricted

Functions with non-private visibility are called with their corresponding fully qualified path relative to the crate root. The macro will require the module path to the function relative to the crate root.

For functions defined in the crate root, use crate as a path instead. These macros can be used inside or imported by other crates as the macro substitutes metavariables.

// public vis in root scope
#[defamed::defamed(crate)]
pub fn root_scope() {}

pub mod inner {
    // restricted vis in module `inner`
    #[defamed::defamed(inner)]
    pub(crate) fn crate_scope() {}
}

// macros resolve to:
crate_scope!() => $crate::inner::crate_scope()
root_scope!() => $crate::root_scope()

Struct field visibility

Struct fields must be at least as visible as the struct itself. Public structs may be constructed by external crates, so the macro will require all fields to be public.

Valid examples:

#[defamed::defamed]
struct Private {
    field: i32
}

// restricted visibility or higher
#[defamed::defamed(crate)]
pub(crate) struct Restricted {
    #[def]
    pub field: i32
}

#[defamed::defamed(crate)]
pub struct Public {
    #[def]
    pub field: i32
}

#[defamed::defamed(crate)]
pub struct PublicTuple(pub i32, #[def] pub i32);

Invalid examples:

#[defamed::defamed(crate)]
pub struct Public {
    pub field: i32
}

#[defamed::defamed(crate)]
pub struct InvalidOrder {
    /// default fields must be defined last - compile error
    #[def]
    pub field_a: i32,
    pub field_b: u32,
}

// all fields must be public
#[defamed::defamed(crate)]
pub struct PublicTuple(pub i32, #[def] i32);

// will not compile - unit structs do not have any fields
#[defamed::defamed]
struct UnitStruct;

Macro generation size

[!CAUTION] The size of the macro generated (number of match arms) is exponentially related to $max(positional, default)$. This is because the macro contains all permutations of positional and default parameters.

It is recommended that items do not exceed 9 positional and/or 9 default parameters. Exceeding this number will cause the build times to increase significantly.

Benefits

  • Better ergonomics
  • More clarity during code reviews
  • Seamless addition of default parameters to existing items without breaking compatibility

Limitations

  • applicable for standalone functions defined outside of an impl block
  • requires specifying fully qualified module path to item
  • renaming parameters requires updating all macro invocations

Dependencies

~250–690KB
~16K SLoC