#traits #variables #macro #code-generation #proc-macro #type

构建 trait_variable

在特质定义中使变量字段成为可行

11 个版本 (6 个破坏性更新)

0.7.0 2024 年 4 月 19 日
0.6.0 2024 年 3 月 9 日
0.5.0 2024 年 3 月 7 日
0.4.3 2024 年 2 月 18 日
0.1.0 2024 年 1 月 15 日

#158 in 构建工具

Download history 191/week @ 2024-04-08 154/week @ 2024-04-15 14/week @ 2024-04-22

每月 1,062 次下载

MIT/Apache

56KB
989

特质变量

为 Rust 使特质定义中的变量字段成为可行。

具体来说,此软件包通过过程宏以及结构体,在特质定义中启用变量字段的用法,模拟了像 C++ 和 Python 这样的语言中发现的继承。此功能在不修改 Rust 的核心语言特性的情况下实现,提供了一种解决方案,直到类似的特性可能正式引入 Rust。

用法

要使用此软件包,将其添加到您的 Cargo.toml

[dependencies]
trait_variable = "*"

然后,按照以下方式将宏集成到您的 Rust 代码中

/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓trait definition↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
use trait_variable::{trait_var, trait_variable};
trait_variable! {
    pub(crate) trait MyTrait {  // feel free to add `pub` when needed
        // 1.put the variable fields definition at the VERY TOP of the target trait before any vaild trait item
            x: i32;
        pub y: bool;

        // 2.the order of the original valid trait items doesn't matter
        fn print_x(&self){
            println!("x: `{}`", self.x);
        }
        fn print_y(&self){
            println!("y: `{}`", self.y);
        }
        fn print_all(&self);
    }
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑trait definition↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/

/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓struct definition↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
// way1: use the attribute macro to expand the struct (Recommended)
#[trait_var(MyTrait)]
pub struct MyStruct {
    a: i32,
    pub b: String,
}
// way2: use the hidden declarative macro to expand the struct (Not recommended)
// MyTrait_for_struct! {
//     pub struct MyStruct { // feel free to add `pub` when needed
//      // feel free to add any fields as usual or leave it empty
//      a: i32,
//      pub b: String,
//     }
// }
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑struct definition↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/

/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓struct impl↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
// the struct basic implementation
impl MyStruct {
    pub fn new(a: i32, b: String, x: i32, y: bool) -> Self {
        Self { a, b, x, y }
    }
}
// the trait implementation
impl MyTrait for MyStruct {
    fn print_all(&self) {
        println!("a: `{}`", self.a);
        println!("b: `{}`", self.b);
        self.print_x();
        println!("y: `{}`", self.y);
    }
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑struct impl↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/

#[test]
fn test() {
    let s = MyStruct::new(1, "hello".into(), -2, true);
    s.print_all();
    assert_eq!(s.x, -2); // if not in a unit test, then `self.x`` is not accessible, since it is private
    assert_eq!(s.y, true); // s.x is private
}

技术细节

上述示例演示了如何定义具有变量字段的特质并为结构体实现它。`trait_variable!` 宏简化了在特质中包含变量字段的过程,而 `#[trait_var]` 属性宏自动处理实现细节,允许结构体使用这些字段就像它们自己的字段一样。有关完整示例,请参阅 基本示例(还有更多全面且实用的示例,一个更复杂的示例,包含泛型,您可以参考)

具体来说,`x` 和 `y` 是特质 `MyTrait` 的变量字段,`MyStruct` 是实现了 `MyTrait` 的结构体。`trait_variable!` 功能宏会自动将特质变量转移到以 `_` 作为前缀的新父特质(在本例中为 `_MyTrait`)。以下是如何为 `_MyTrait` 生成默认实现的示例

pub(crate) trait _MyTrait {
    fn _x(&self) -> &i32;
    fn _x_mut(&self) -> &mut i32;
    fn _y(&self) -> &bool;
    fn _y_mut(&self) -> &mut bool;
}
pub(crate) trait MyTrait: _MyTrait {
  // 1. the trait variable is erased here
  // 2. the original trait definition is kept here
  // ...
}

通过这种方式,在使用特征变量的基础构建过程中,不会违反任何规则。可以很容易地猜测,任何以 self.x 作为特征变量标识符的特征函数体将被转换为 self._x()self._x_mut()

此外,#[trait_var(MyTrait)] 属性宏首先将目标结构体扩展为上述块 way2: 中所述的,由隐藏声明宏(同样由 trait_variable! 生成)包裹的形式,同时包含一个指向隐藏特征路径的导入语句。然后这个隐藏声明宏将进一步为 MyStruct 生成 _MyTrait(不是 MyTrait)的默认实现。

use crate::PathToMyTrait; // if the trait is defined in another file
impl _MyTrait for MyStruct {
    fn  _x(&self) -> &i32 {
        &self.x
    }
    fn  _x_mut(&self) -> &mut i32 {
        &mut self.x
    }
    fn _y(&self) -> &bool {
        &self.y
    }
    fn _y_mut(&self) -> &mut bool {
        &mut self.y
    }
}

最后,xy 可以像 MyStruct 的字段一样访问,好像它们是 MyStruct 的字段一样!没有其他字段代码!干杯~ ^_^;可选地,可以像通常一样为 MyTrait 中定义的方法实现 MyStruct。(注意:如果结构体定义在定义特征的文件之外的文件中,需要在定义结构体的文件开头添加 use <trait_module>::<hidden_parent_trait_name> -- 在这种情况下,<hidden_parent_trait_name>_MyTrait。)

注意:在整个实现过程中,xy 仍然是 MyTrait 的字段,但从技术上讲是 MyStruct 的字段。也就是说,这里没有任何继承机制,因为 xy 的状态/操作仅在实现的结构体中处理--是的,结构体仍然像通常一样通过接口(特征)定义,而不是通过继承。

局限性

trait_variable 宏内部的代码可能无法从 Rust 扩展(如 Rust Analyzer)获得全面的智能提示和 lint 支持。这是因为大多数 Rust 扩展目前难以有效地处理宏内部的标识符。因此,当使用此 crate 时,您可能无法访问完整的代码补全、重构、转到定义和其他智能感知功能。这是一个已知的局限性,未来版本可能会得到改进(如果技术上可行)。

此外,由于声明性宏的一些问题,目前,如果两个宏在同一文件中使用,则 trait_variable! 宏应放在 #[trait_var(MyTrait)] 属性宏之前,否则隐藏特征 _MyTrait 将无法找到。这个问题将在未来得到解决。

要求

  • 需要 Rust 版本 2021 或更高。
  • 该仓库已经使用Rust版本1.77.1进行了测试,但未指定最低兼容版本。

贡献

欢迎贡献。请随意提交pull请求或在GitHub仓库上打开问题。

待办事项列表

  • [] 在结构体定义中处理trait bound(where子句);
  • [] 在结构体定义中使用生命周期;
  • [] 在结构体定义中使用具有数组字段的常量泛型;
  • [] 检查函数replace_self_field的参数必要性;
  • [] 在trait定义中使用GAT、异步方法等;
  • [] 尝试让智能智能感应扩展(如Rust Analyzer)支持宏内部的标识符(可能不可能)。

许可证

此仓库可以在以下两种许可证下使用:

选择最适合您需求的许可证。

依赖关系

~2–11MB
~111K SLoC