#proc-macro #named-arguments #default #macro-generates #arg #syntax #nightly

rubber_duck

启用使用命名参数语法调用函数的功能

4个版本

0.2.2 2018年12月20日
0.2.1 2018年12月19日
0.2.0 2018年12月19日
0.1.0 2018年9月14日

#287过程宏

MIT 协议

16KB
148

概览

此crate提供了一个伪实现,实现了许多命名参数RFC之一。由于宏的使用而不是真正的编译器支持,它与实际的事物并不完全相同。此crate提供了一个类似于属性的进程宏,用于注解函数以生成命名参数支持。

该注解执行以下操作:

  • 生成一个与函数具有相同参数的struct
    • 以及一个关联的builder()函数
    • 默认情况下,此struct隐藏在文档中
  • 为该struct方法生成builder
    • 它是一个类型安全的builder,要求每个没有默认值的函数都必须调用
    • build函数将builder转换回参数struct
    • 此struct也默认隐藏在文档中
  • 可以在注解中为每个字段指定默认值
    • 字段类型被Option包装(例如,T从T变为Option)
    • 在builder上指定None作为默认值
    • 并且具有Option类型,字段setter的builder接受T或Option
  • 还可以指定位置参数(不允许为这些参数指定默认值)
  • 生成与函数同名的一个宏,该宏接受命名参数(仅在nightly版本中支持)
    • 创建builder
    • 设置传入的任何参数
    • 调用build(如果没有提供所有命名值,则在没有默认值的情况下将是一个编译错误)
  • 还有一个退出宏 —— n!,您可以将函数调用包裹在其中以启用命名/默认参数语法。

所以,我们如何在几个命名空间(fn、struct和宏)中重载方法名称,以便消费者可以使用纯fn或宏

限制

本实现放弃了声明方面的处理 - 例如如何声明参数为命名参数以及如何指定参数默认值。它而是在属性中指定所有这些,以避免解析问题。未来的版本可以从类似属性的 proc macro 切换到类似函数的 proc macro,以实际实验声明语法。

这个版本对泛型处理得不好。这似乎是这个方法的一个可解决的限制。

这个版本只适用于独立函数,不适用于 impl 块中的函数。这也应该可以解决。

这个版本需要 nightly(由于 decl. macro 2.0 和 proc_macro_gen)以及 2018 版本(由于宏路径)。虽然 proc_macro_gen 可能很快就会稳定,但 decl. macro 2.0 不会(我认为)。decl. Macros 2.0 是用来代替 macro_rules 的,以便为生成的宏提供正确的模块命名空间支持。

可能是由于错误造成的。

从 rubber_duck 导出的宏没有出现在 rubber_duck 文档中。

为 api declarer 生成的宏没有在生成的文档的正确模块中显示 GitHub 问题 #54112

使用

先决条件

  • 使用 edition 2018(不同版本之间的宏路径与我所做的工作交互非常差)

Nightly 与 Not-Nighlty

这个 crate 有一些仅在 nightly 中可用的功能。

到目前为止,还没有夜间与非夜间功能的检测。相反,在声明您想使用命名/默认参数语法调用的函数的 crate 中,您必须通过使用 features=["nightly"] 来选择加入夜间仅有的功能。

编写 API

要设置一个 crate,以便发布可以使用命名/默认参数语法调用的方法,您需要对 Cargo.toml 和 lib.rs 进行一些初始设置工作。然后您将能够注释方法。任何消耗 crate 都不需要做任何不同的事情。

设置

如果您想使用夜间功能,请将此添加到您的 Cargo.toml 中

[dependencies]
rubber_duck = { version="0.2", features=["nightly"]}

否则,如果您在使用稳定版本,请添加此内容

[dependencies]
rubber_duck = "0.2"

然后在您的 lib.rs 的顶部添加此内容

// The following two lines (well, 4 if you count comments) are only needed when using features=["nightly"]
// Allows us to generate macros from macros
#![feature(proc_macro_hygiene)]
// Allows the use of declarative macros 2.0 (which are generated from the proc macro
#![feature(decl_macro)]

// These lines are needed on both nightly and stable
// Hide the base items the macro internals use
#[doc(hidden)]
// Import the base items the macro internals use
// This must be at the base of the crate
pub use rubber_duck::core::*;

注释函数

要使函数可以用命名语法调用,在函数所在的模块中导入宏

use ::rubber_duck::macros::*;

然后使用 #[gen_struct_sugar] 注释一个函数 - 这将足以生成一个可以以命名语法调用的宏。

此外,您可以声明 a) 位置参数和 b) 命名参数的默认值

#[gen_struct_sugar(
       defaults( // You don't have to set defaults for all named parameters, but here we do
           read = "false",
           write = "false",
           append = "false",
           truncate = "false",
           create = "false",
           create_new = "false",
       ),
       positionals(path), // Here we list the positional parameters in the order they appear
   )]
   pub fn open_file(
       path: PathBuf,
       read: bool,
       write: bool,
       append: bool,
       truncate: bool,
       create: bool,
       create_new: bool,
   ) -> std::io::Result<File> {
       OpenOptions::new()
           .read(read)
           .write(write)
           .append(append)
           .truncate(truncate)
           .create(create)
           .create_new(create_new)
           .open(path)
   }

消耗 API

调用方法时,使用感叹号! - 任何位置参数都必须以正确的顺序先于命名参数,不使用名称,命名参数以name => value的形式出现,其中name是文档中的发布参数名称

此外,消费者与写入器在不同的crate中,它们不需要启用任何功能标志

尽管如此,它们仍然需要在nightly上,可能是2018版(因为路径...)

完整示例

给定API声明

// Given this api declaration:
mod module {
   #[gen_struct_sugar(
        defaults(greeting = r#""Hello.""#),
        positionals(name),
    )]
   pub fn is_a_test(name: &'static str, greeting: &'static str, message: &'static str) -> String {
       format!("Dear {}, {}. {}", &name, &greeting, &message)
   }
}

// One can call the function in a variety of ways
mod stable {
    use crate::{n,module::is_a_test};
    // Named form requires a macro
    n!(is_a_test{"George", {greeting: "Hi.", message: "Rust is cool."}});   // Dear George, Hi. Rust is cool.
    // and lets you use defaults
    n!(is_a_test{"George", {message: "Hi."}});                              // Dear George, Hello. Rust is cool.
    // and even lets you use sugar (you need a trailing comma if there's only one named arg
    let message = "Struct Sugar";                                           // Dear George, Hello. Struct Sugar
    n!(is_a_test{"George", {message,}})

    // Positional form doesn't need a macro, but args with defaults are wrapped in the option type
    // Override the default
    is_a_test("Bob", Some("Hi."), "Goodbye.");                              // Dear Bob, Hi. Goodbye.
    // Use the default
    is_a_test("Bob", None, "Goodbye.");                                     // Dear Bob, Hello. Goodbye.
}

//There's also a slightly nicer way on nightly (behind the features=["nightly"] flag)
mod nightly_only {
    use crate::module::is_a_test;
     // Named form requires a macro
    is_a_test!("George", greeting=> "Hi.", message=> "Rust is cool");        // Dear George, Hi. Rust is cool.
     // and lets you use defaults
    is_a_test!("George", message=> "Rust is cool");                          // Dear George, Hello. Rust is cool.
}
// You don't even have to import it!
crate::module::is_a_test!("George", greeting=> "Hi.", message=> "Rust is cool");

此外,请查看github仓库中的example_api和example_consumer目录。

依赖项

~2MB
~45K SLoC