rust UI框架-druid UI-Lens绑定数据-还不赶快学起来

一个关键的抽象Druid随着数据是Lens trait。本页解释它们是什么,以及如何使用它们。一开始,Lens可能看起来很复杂,但是它们也非常强大,允许你编写可重用、简洁和易懂的代码(一旦你理解了lens本身)。
基本原理
让我们从镜头的定义开始:

pub trait Lens<T, U> {
    fn with<F: FnOnce(&U)>(&self, data: &T, f: F);

    fn with_mut<F: FnOnce(&mut U)>(&self, data: &mut T, f: F);
}

我从Druid的源代码中复制了这个定义,然后通过删除返回类型来简化它,因为它们不是Lens工作方式的基础。
首先要注意的是Lens本身的泛型。Lens有三种类型: Lens、LensT 和Lens U。这两个类型的参数表示Lens所解决的不匹配: 我们有一个操作于 U 的函数,和一个 T 类型的对象,所以我们需要以某种方式将 T 转换成 U。

实现
举个例子。让我们手动实现和使用镜头,这样我们就可以看到发生了什么。

struct Container {
    inner: String,
    another: String,
}

// Here the lens doesn't have any data, but there are cases where
// it might, for example it might contain an index into a collection.
struct InnerLens;

// Our lens will apply functions that operate on a `String` to a `Container`.
impl Lens<Container, String> for InnerLens {
    fn with<F: FnOnce(&String)>(&self, data: &Container, f: F) {
        f(&data.inner);
    }

    fn with_mut<F: FnOnce(&mut String)>(&self, data: &mut Container, f: F) {
        f(&mut data.inner);
    }
}

这个案子很简单。我们需要做的就是将函数投射到字段上。请注意,这不是我们可以制作的从 Container 到 String 的唯一有效镜头-我们也可以从 Container 投影到另一个镜头。在实现 Lens 时,我们选择了如何将 Container 转换为 String。
你还会注意到,这两个方法都接受对 self 的不可变引用,甚至是 mut 变量。Lens本身应该被认为是一个固定的东西,知道如何进行映射。在上述情况下,它不包含任何数据,甚至很可能不会出现在最终编译/优化的代码中。

现在来看一个稍微复杂一点的例子

struct Container2 {
    first_name: String,
    last_name: String,
    age: u16, // in the future maybe people will live past 256?
}

struct Name {
    first: String,
    last: String,
}

struct NameLens;

impl Lens<Container2, Name> for NameLens {
    fn with<F: FnOnce(&Name)>(&self, data: &Container2, f: F) {
        let first = data.first_name.clone();
        let last = data.last_name.clone();
        f(&Name { first, last });
    }

    fn with_mut<F: FnOnce(&mut Name)>(&self, data: &mut Container2, f: F) {
        let first = data.first_name.clone();
        let last = data.last_name.clone();
        let mut name = Name { first, last };
        f(&mut name);
        data.first_name = name.first;
        data.last_name = name.last;
    }
}

现在我相信你已经意识到了,以上这些是非常低效的。考虑到我们将经常遍历我们的数据,我们需要它是便宜的。(这在以前不是问题,因为当我们不需要构建内部类型时,我们可以只使用引用。如果我们的数据复制/克隆成本很低,例如任何基本数字类型 u8,… f64,那也不是问题。)幸运的是,这正是生锈所擅长的。让我们重写上面的例子,以快速!

struct Container2 {
    first_name: Rc<String>,
    last_name: Rc<String>,
    age: u16,
}

struct Name {
    first: Rc<String>,
    last: Rc<String>,
}

struct NameLens;

impl Lens<Container2, Name> for NameLens {
    // .. identical to previous example
}

正如您将看到的,我们已经介绍了 Rc: 引用计数指针。您将在示例中看到这个函数及其多线程表兄 Arc 被广泛使用。现在,我们实际复制内存的唯一时间是在 f in with _ mut 中调用 Rc: : make _ mut 时。这意味着,在没有任何变化的情况下,我们所要做的就是递增和递减引用计数。此外,我们还为编译器提供了内联 f 和/with _ mut 的机会,使得这个抽象可能成本为零
折衷之处在于,我们在 Name 类型中引入了更多的复杂性: 为了对数据进行更改,我们必须使用 Rc: : make _ mut 来获得对 String 的可变访问权。(镜头中的代码将确保 Rc 数据的较新副本保存到外部类型。)这意味着快速编写德鲁伊代码需要了解铁锈指针类型(Rc/Arc,也可能是 RefCell/Mutex)。
我们可以做得更好。假设我们处理的是数据向量而不是字符串。我们可以导入入口箱来获得使用结构共享的集合,这意味着即使载体发生变异,我们也只克隆需要克隆的部分。因为 IM 非常有用,所以它包含在Druid(在 IM 特性后面)中。

struct Container2 {
    // Pretend that it's the 1980s and we store only ASCII names.
    first_name: im::Vector<u8>,
    last_name: im::Vector<u8>,
    age: u16,
}

struct Name {
    first: im::Vector<u8>,
    last: im::Vector<u8>,
}

struct NameLens;

impl Lens<Container2, Name> for NameLens {
    // .. identical to previous example
}

现在,除了几乎免费的克隆,我们还可以对数据本身进行廉价的增量更新。对于名称,这并不重要,但是如果向量有1 _ 000 _ 000 _ 000个元素,我们仍然可以在 O (log (n))时间内进行更改(在这种情况下,1 _ 000 _ 000 _ 000和30之间的差异非常大!).
好了,现在你知道Lens是怎么工作的了。恭喜你,你已经完成了最困难的部分!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若梦网络编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值