#声明式 #构建器 #领域特定语言 #模板 #结构字面量

nightly template-builder

一个用于创建符合惯用、声明式、构建器样式的 Rust 库,使用结构字面量语法。

1 个不稳定版本

0.1.0 2022 年 6 月 21 日

#2568Rust 模式

LGPL-3.0

8KB
60

Template.rs

一个用于创建符合惯用、声明式、构建器样式的 Rust 库,使用结构字面量语法。

无需宏函数!

如何使用

要创建一个模板,就像创建一个构建器一样,你必须考虑以下因素

  1. 所有参数
  2. 模板的默认状态
  3. 模板构建成什么
  4. 与其他对象的关系

你可以使用 #[template] 注释来定义你的模板模型结构体,以及 Template 特性。

1. 定义参数

只需在具有所有参数作为其字段的 struct 上使用 #[template] 注释即可

#[template]
pub struct Box {
    orientation: Orientation,
    spacing: i32,
    padding: i32,
    margin: i32,
}

#[template]
pub struct Button {
    padding: i32,
    margin: i32,
    style: StyleDescriptor,
    text: String
}

2. 模板的默认状态

默认情况下,此库通过 #[derive(Default)] 定义模板的默认状态。也计划提供自定义 Default 实现。

3. 模板构建成什么

使用 Template 特性来定义模板构建成什么,以及如何构建


impl Template for Box {
    type Output = some_other_lib::Box;
    
    fn define(self) -> Self::Output {
        let mut this = some_other_lib::Box::new();
        this.padding = self.padding;
        //...
        this
    }
}

impl Template for Button {
    type Output = some_other_lib::Button;
    
    fn define(self) -> Self::Output {
        let mut this = some_other_lib::Button::new();
        this.padding = self.padding;
        //...
        this
    }
}

与其他对象的关系

通过模板的目标对象进行依赖注入,定义不同的默认状态。

trait Container {
    fn child<T, W>(&self) -> T
        where T: Template<Output = W>,
              W: some_other_lib::Widget;
}

impl Container for some_other_lib::Box {
    fn child<T, W>(&self) -> T
        where T: Template<Output = W>,
              W: some_other_lib::Widget 
    {
        let this = self.clone();
        let out = Button::default();            // The template
        out.on_create(move |w| this.add(w));    // provided by the #[template] annotation
        out
    }
}

使用模板

所有注释过的模板实现了以下特性,用于构建目标类型

  • for<A,F: FnOnce(Self::Output) ->A> FnOnce(F) ->A
  • FnOnce() -> Self::Output

这些特性可以在结构字面量之后以“柯里化”的方式调用。

这是一个简单示例,展示了惯用模板

Box {
    orientation: Orientation::HORIZONTAL,
    padding: 6,
    spacing: 6,
    ..Default::default()
} (|w| {
    Box {
        orientation: Orientation::VERTICAL,
        spacing: 6,
        ..w.child()
    } (|w| {

        Button {
            text: "Column btn 1",
            ..w.child()
        }();    
        // Function call constructs button 
        // and adds it to the box as per the
        // ..w.child() injection directive

        Button {
            text: "Column btn 2"
            ..w.child()
        }();

    }); // function call runs the lambda argument
        // on the output and then returns it

    Button {
        text: "Big btn",
        ..w.child()
    }();
})

// expected result:
// |--------------|---------|
// | Column btn 1 |         |
// |--------------| Big Btn |
// | Column btn 2 |         |
// |--------------|---------|

如果你不想使用直接函数调用,可以使用以下等效方法

  • fn build<A>(self, F:impl FnOnce(Self::Output)-> A) ->A
  • fn create(self) -> Self::Output
Box {
    orientation: Orientation::HORIZONTAL,
    padding: 6,
    spacing: 6,
    ..Default::default()
}.build(|w| {       // creations with lambdas use the "build" method
    Button {
        text: "Hello World",
        ..w.child()
    }.create();     // creations without arguments use the "create" method
})

当前问题和未来计划

  1. 允许用户为模板定义自定义默认状态
  2. 创建一个Templatable特性,将模板的输出与其模板关联起来,这样您就无需自行关联。例如。
trait Templatable {
    type New: Template<Output = Self>;
}

// ...so that

use some_other_lib::Box;

Box::New {
    //...
} ();
  1. 目前一些类型分析器不考虑std::ops::FnOnce实现,因此它们会自动将Struct { /*...*/ } ( /*...*/ )语法标记为错误,尽管按照std::ops::FnOnce特性定义,这些语法是完全合法和正确的。例如,在jetbrains Rust插件中。在这些问题解决之前,您可以选择使用不同的分析器,或者使用替代的.build().create()方法。

依赖项

~7KB