Skip to content
/ nade Public

Adding named and default arguments to Rust functions -- 为 Rust 函数添加命名参数和默认参数

License

Notifications You must be signed in to change notification settings

ZihanType/nade

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nade

Crates.io version docs.rs docs

English | 简体中文

nade is a attribute macro that adds named and default arguments to Rust functions.

Usage

// some_crate/src/lib.rs
pub use nade::base::*;
use nade::nade;

pub fn one() -> u32 {
    1
}

#[nade]
pub fn foo(
    /// You can add doc comments to the parameter. It will be shown in the doc of the macro.
    ///
    /// The world is 42.
    #[nade(42)] a: u32,

    /// Call a function
    #[nade(one())] b: u32,

    /// Default value of u32
    #[nade] c: u32,

    d: u32
) -> u32 {
    a + b + c + d
}

assert_eq!(foo!(1, 2, 3, 4), 10);         // foo(1,  2,     3,                  4)
assert_eq!(foo!(d = 2), 45);              // foo(42, one(), Default::default(), 2)
assert_eq!(foo!(1, c = 2, b = 3, 4), 10); // foo(1,  3,     2,                  4)

How it works

If you write a function like this:

// some_crate/src/lib.rs
pub use nade::base::*;
use nade::nade;

pub fn one() -> u32 {
    1
}

#[nade]
pub fn foo(
    #[nade(42)]
    a: u32,

    #[nade(one())]
    b: u32,

    #[nade]
    c: u32,

    d: u32
) -> u32 {
    a + b + c + d
}

it will be expanded to:

// some_crate/src/lib.rs
// ⓵
pub use nade::base::*;
use nade::nade;

pub fn one() -> u32 {
    1
}

pub fn foo(a: u32, b: u32, c: u32, d: u32) -> u32 {
    a + b + c + d
}

// ⓶
#[::nade::__internal::macro_v(pub)]
macro_rules! foo {
    ($($arguments:tt)*) => {
        // ⓷
        $crate::nade_helper!(
            ($($arguments)*)
            (a: u32 = 42, b: u32 = one(), c: u32 = Default::default(), d: u32)
            (foo)
        )
    };
}

Then, when you call the macro foo like this:

use some_crate::{foo, one};

foo!(32, d = 1, c = 2);

it will be expanded to:

use some_crate::{foo, one};

foo(32, one(), 2, 1);

Note

As you can see in How it works, there are 3 things to be aware of in the code generated by #[nade].

  • ⓵, ⓷

    nade_helper is a declarative macro used to generate function call expressions based on arguments, parameters, and function path.

    Its path defaults is $crate::nade_helper, so you need to import the macro in the root of crate using pub use nade::base::*; or pub use nade::base::nade_helper;.

    Also you can customize the path of nade_helper.

    use nade::nade;
    
    mod custom_nade_helper {
        pub use nade::base::nade_helper;
    }
    
    #[nade]
    #[nade_path(nade_helper = custom_nade_helper)]
    fn custom_nade_helper_path(a: usize) -> usize {
        a
    }
  • macro_v is an attribute macro that makes the visibility of the declarative macro the same as the function. see macro-v for details.

    Its path defaults is ::nade::__internal::macro_v.

    Also you can customize the path of macro_v.

    use nade::nade;
    
    mod custom_macro_v {
        pub use nade::__internal::macro_v;
    }
    
    #[nade]
    #[nade_path(macro_v = custom_macro_v)]
    fn custom_macro_v_path(a: usize) -> usize {
        a
    }

Limitations

  1. When you call the macro foo, you must use the use statement to bring the macro into scope.

    // Good
    use some_crate::{foo, one};
    foo!(32, d = 1, c = 2);
    
    // Bad
    use some_crate::one;
    some_crate::foo!(32, d = 1, c = 2);

    Because the attribute macro #[nade] will generate a macro with the same name as the function, and the macro use the function in an unhygienic way, so you must use the use statement to bring the macro and the function into scope.

  2. The default argument expression must be imported into the scope of the macro call.

    // Good
    use some_crate::{foo, one};
    foo!(32, d = 1, c = 2);
    
    // Bad
    use some_crate::foo;
    foo!(32, d = 1, c = 2);

    Because the default argument expression is evaluated after the foo macro is expanded, so it must be imported into the scope of the macro call.

How to bypass the limitations

  1. You can pass a module path starting with $crate for the #[nade] attribute macro on the function.

    #[nade(module_path = $crate::module)] // <--- here
    pub fn foo(
        #[nade(42)]
        a: u32,
    
        #[nade(one())]
        b: u32,
    
        #[nade]
        c: u32,
    
        d: u32
    ) -> u32 {
        a + b + c + d
    }

    it will be expanded to:

    pub fn foo(a: u32, b: u32, c: u32, d: u32) -> u32 {
        a + b + c + d
    }
    
    #[::nade::__internal::macro_v(pub)]
    macro_rules! foo {
        ($($arguments:tt)*) => {
            $crate::nade_helper!(
                ($($arguments)*)
                (a: u32 = 42, b: u32 = one(), c: u32 = Default::default(), d: u32)
                ($crate::module::foo) // <--- here
            )
        };
    }

    Then, you can not use the use statement to bring the macro and the function into scope, like this:

    use some_crate::one;
    some_crate::foo!(32, d = 1, c = 2);
  2. In the #[nade] attribute macro on the parameter, you can specify the default argument expression using the full path, either $crate::a::expr, or ::a::b::expr. In fact, when you use #[nade] on an parameter, you are using #[nade(::core::default::Default::default())].

    pub fn one() -> u32 {
        1
    }
    
    pub static PATH: &str = "a";
    
    #[nade]
    pub fn foo<T1, T2, T3, T4>(
        #[nade($crate::module::one())]
        a: T1,
    
        #[nade(::std::path::Path::new("a"))]
        b: T2,
    
        #[nade($crate::module::PATH)]
        c: T3,
    
        #[nade("Hello")]
        d: T4
    ) {
        let _ = (a, b, c, d);
    }

    it will be expanded to:

    pub fn foo<T1, T2, T3, T4>(a: T1, b: T2, c: T3, d: T4) {
        let _ = (a, b, c, d);
    }
    
    #[::nade::__internal::macro_v(pub)]
    macro_rules! foo {
        ($($arguments:tt)*) => {
            $crate::nade_helper!(
                ($($arguments)*)
                (
                    a: T1 = $crate::module::one(),
                    b: T2 = ::std::path::Path::new("a"),
                    c: T3 = $crate::module::PATH,
                    d: T4 = "Hello",
                )
                (foo)
            )
        };
    }

    Then, you can not use the use statement to bring default argument expressions into scope, like this:

    use some_crate::foo;
    foo!();

Credits

This crate is inspired by these crates:

About

Adding named and default arguments to Rust functions -- 为 Rust 函数添加命名参数和默认参数

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages