16个版本

0.2.4 2022年8月8日
0.2.3 2022年8月5日
0.2.1 2022年7月17日
0.1.0 2022年5月30日
0.0.1-alpha2022年5月26日

#1264过程宏

Download history 7771/week @ 2024-03-14 7477/week @ 2024-03-21 5389/week @ 2024-03-28 8429/week @ 2024-04-04 7261/week @ 2024-04-11 12154/week @ 2024-04-18 14755/week @ 2024-04-25 13518/week @ 2024-05-02 16032/week @ 2024-05-09 17022/week @ 2024-05-16 17449/week @ 2024-05-23 19889/week @ 2024-05-30 14981/week @ 2024-06-06 14258/week @ 2024-06-13 18398/week @ 2024-06-20 15674/week @ 2024-06-27

67,227 每月下载量
20 个crate中使用了(通过 nougat

Zlib OR MIT OR Apache-2.0

40KB
1K SLoC

::nougat nougat logo

在稳定的Rust上使用(生命周期)GATs。

Repository Latest version Documentation MSRV unsafe forbidden License CI

示例

#![forbid(unsafe_code)]
# use ::core::convert::TryInto;

#[macro_use]
extern crate nougat;

#[gat]
trait LendingIterator {
    type Item<'next>
    where
        Self : 'next,
    ;

    fn next(&mut self)
      -> Option<Self::Item<'_>>
    ;
}

struct WindowsMut<Slice, const SIZE: usize> {
    slice: Slice,
    start: usize,
}

#[gat]
impl<'iter, Item, const SIZE: usize>
    LendingIterator
for
    WindowsMut<&'iter mut [Item], SIZE>
{
    type Item<'next>
    where
        Self : 'next,
    =
        &'next mut [Item; SIZE]
    ;

    /// For reference, the signature of `.array_chunks_mut::<SIZE>()`'s
    /// implementation of `Iterator::next()` would be:
    /** ```rust ,ignore
    fn next<'next> (
        self: &'next mut AChunksMut<&'iter mut [Item], SIZE>,
    ) -> Option<&'iter mut [Item; SIZE]> // <- no `'next` nor "lending-ness"! ``` */
    fn next<'next> (
        self: &'next mut WindowsMut<&'iter mut [Item], SIZE>,
    ) -> Option<&'next mut [Item; SIZE]> // <- `'next` instead of `'iter`: lending!
    {
        let to_yield =
            self.slice
                .get_mut(self.start ..)?
                .get_mut(.. SIZE)?
                .try_into() // `&mut [Item]` -> `&mut [Item; SIZE]`
                .expect("slice has the right SIZE")
        ;
        self.start += 1;
        Some(to_yield)
    }
}

fn main() {
    let mut array = [0, 1, 2, 3, 4];
    let slice = &mut array[..];
    // Cumulative sums pattern:
    let mut windows_iter = WindowsMut::<_, 2> { slice, start: 0 };
    while let Some(item) = windows_iter.next() {
        let [fst, ref mut snd] = *item;
        *snd += fst;
    }
    assert_eq!(
        array,
        [0, 1, 3, 6, 10],
    );
}

调试/跟踪宏展开

你可以让宏通过中间生成的文件来执行,以便获得良好的错误信息和可以打开并检查的文件,同时剩余的宏未展开以便于阅读,方法是通过

  1. 启用此依赖项的debug-macros Cargo功能

    [dependencies]
    ## …
    nougat.version = ""
    nougat.features = ["debug-macros"]  # <- ADD THIS
    
  2. 设置环境变量DEBUG_MACROS_LOCATION为某个绝对路径,宏将在此路径下写入生成的文件。

演示

demo

宏是如何工作的?

点击此处查看实现说明

一些历史背景

  1. 2021/02/24: 使用for<'lt> Trait<'lt>作为超特例来模拟GATs的实验

    • (我怀疑甚至可能还有在URLO之前的实验和使用,但我现在找不到它们)

    这已经让GATs几乎完成,但有两个问题,我当时对此表示了不满 😅

    • Trait<'lt>嵌入 所有 关联项,包括方法,而不仅仅是关联的“泛型”类型。

      这可能会带来问题,如果这些其他项依赖于关联类型是完全泛型的,正如我在2021年3月6日的这里所观察到的。

    • 我无法表达where Self : 'next GAT界限。

  2. 2022/03/08:我在这篇官方文章中正式提到了通过类型如&'lt T的隐式界限来解决"late/for-量化where T : 'lt"子句的替代方法。

点击查看更多上下文
  • 这个想法不是我自己想出来的;它有点模糊,但我记得URLO用户steffahn在这方面做了很多工作(例如,这个问题),而且我清楚地记得Kestrer在社区Discord中指出隐式界限黑客攻击

    • 对于那些感兴趣的人来说,我后来使用了这项技术,在一篇非常详细的URLO帖子中,我敢肯定你会对此感兴趣,来解决在更高阶闭包上下文中“过于严格的寿命界限”问题。

    所以,在那时,所有这些成为了URLO的一些常客(如steffahnquinedot)之间的“高级知识”,但从未真正付诸实践:这个想法是等待“合适的解决方案”,即GATs。

  • 尽管如此,我开始考虑这个非常规的crate的想法,当时被称为autogatic

    • 总结

    • 一篇与这个crate当前提供的几乎相同的例子

    • 遗憾的是,这个提案遭到了冷遇:GATs即将稳定,因此一个自动化替代方案的工具被认为是没有用的。

      所以我一直在等待。最后,稳定性问题被打开,然后...某种程度上的“关闭”(更准确地说,推迟到一系列方面可以整理出来,更多信息请参阅该问题)。说实话,我认为现在不稳定的论点相当合理和有根据,即使我仍然希望这个问题在中期内得到稳定。

      这一切都证明了我的autogatic想法是合理的,因此我致力于实现我心中的原型想法:nougat诞生了。

  • 这时,用户Jannis Harder提出了一个关于GATs填充的另一个实现/替代方案。

    1. 使用“标准GAT替代方法”来定义HKT特质

      trait WithLifetime<'lt> {
          type T;
      }
      
      trait HKT : for<'any> WithLifetime<'any> {}
      impl<T : ?Sized + for<'any> WithLifetime<'any>> HKT for T {}
      
    2. 然后,用

      type Assoc : ?Sized + HKT;
      
      • 并使用 <Self::Assoc as WithLifetime<'lt>>::T 来代替 Self::Assoc<'lt>,在解决具有具体生命周期的类型时。
    3. 因此,在实现者方面可以使用

      impl LendingIterator for Thing {
       // type Item
       //     <'next>
       //     = &'next str
       // ;
          type Item           = dyn
              for<'next>      WithLifetime<'next, T
              = &'next str
          >;
          // formatted:
          type Item = dyn for<'next> WithLifetime<'next, T = &'next str>;
      }
      
      • (或者使用 for<> fn 指针,但实际上它们并不像 dyn for<> Trait 那样工作得很好)

    这种方法有一些缺点(隐式界限更难(但不是不可能!)挤进去),并且当 Assoc<'lt> 有自己的界限时,似乎需要一种专门的 HKT trait,它具有对 T 的这种界限。

    话虽如此,这种基于 HKT 的方法有一个优点,即它是唯一一个能够在一定程度上与 dyn 友好的(-ish),而“经典解决方案”方法则不是这样。

    请参阅下面的 Sabrina Jewson 的博客文章,以了解这两种方法的更深入比较。

实际解释

当我准备花几个小时详细说明这些技巧时 😅,幸运的是,我得知有人已经做了所有这些工作,而且文笔比我好得多:Sabrina Jewson 🙏。她写了一篇非常完整和详尽的博客文章,关于 GAT、它们的稳定补丁,以及它们是如何相互比较的(有趣的是,GAT 当前 它们的补丁更差,因为由于编译器错误,每次向 GAT 添加一个 trait 约束时,相关的 GAT 都不得不成为 : 'static,没有任何实际原因,只是编译器在这件事上出了问题)。

以下是该博客文章的链接,直接指向该 crate 正在使用的工作区,但您也可以删除锚点并阅读全文,它绝对值得一看

📕 Lifetime GAT 的更好替代方案 – 由 Sabrina Jewson 编写 📕


限制

  • 仅支持 生命周期 GAT(不支持 type Assoc<T>type Assoc<const>)。

  • 宏生成的代码目前 完全不 dyn 友好。这可能会在将来得到改善;可能使用另一种实现方式的解糖。

  • 为了使用 Gat!#[gat] 注解的项目外引用 GAT,需要这样做。

  • 在函数中将 trait 约束添加到 GAT 会破坏该函数的类型推理(感谢 Discord 用户 Globi 识别和报告此问题)


lib.rs:

该 crate 不打算直接使用。请使用 https://docs.rs/nougat。

依赖项

~1.5MB
~37K SLoC