Rust: 🔬 泛型关联类型 (GAT) 的跟踪问题

创建于 2017-09-02  ·  67评论  ·  资料来源: rust-lang/rust

最有用的评论

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 是一个(有点简洁的)更新。

67510 是最后一个需要实现的主要 ICE/缺失功能。

所有67条评论

这是我将努力保持更新的一种实施计划。

  • [ ] 第一步:添加对 AST 和漂亮打印的支持

    • 第一步可能是开始解析新表单并将对它们的支持添加到 AST。

    • 在此处查看此评论以获取更详细的想法

    • 我们应该能够编写一些仅解析测试并测试漂亮的打印机 hir

    • 当我们达到降低 HIR时,如果存在任何 GAT,我们就会出错

    • 我们也可以做特征门然后

  • [ ] 更多

让我从更详细地介绍 AST 开始。 首先让我们讨论一下它今天的工作原理:

特征定义中的type Foo: Bar [= Baz];由此 AST 变体定义。 这包括边界( Bar )和(可选)默认值Baz 。 该名称在TraitItem结构中定义。

一个特征 impl 中的type Foo = Bar;项是由这个 AST 变体定义的——它只包括类型Bar ,因为Foo等是在ImplItem中定义的

方法是一个有趣的例子,因为它们已经可以被通用化。 这些通用参数MethodSig结构的Generics字段中声明。 这是Generics结构的一个实例。

我的看法是,最好的办法是将Generics方法中“提升”到TraitItem (和ImplItem ),以便它同样适用于所有形式的trait 和 impl 项目。 目前,我们不支持泛型常量,我猜,但老实说,无论如何它们可能只是从我们正在做的工作中脱离出来,所以它们只是一个小的扩展。 我认为如果我们现在就为他们做计划,工作会变得更好。

也许一个体面的第一个 PR就是做那个改变,保持所有其他现有功能不变。 也就是说,我们会将更多的Generics放入TraitItem (和ImplItem )和MethodSig之外。 我们将为非方法提供一个空的Generics 。 我们将通过现有代码工作,根据需要使用泛型以使其工作。

@nikomatsakis酷! 太感谢了! 我昨晚开始尝试这个,我很自豪地说我找到了你在评论中提到的关于 AST 的相同地方。 :smile: (鉴于这是我第一次接触 rustc,我认为这是一项成就!)

我不认为将泛型提升到TraitItem 。 我的方法是将Generics放入TraitItemKind::Type中,因为那里已经存储了类型声明。 你的方法也很有意义,所以我会努力实现它。 由于我对这个代码库仍然是全新的,我很想知道如果我的方法被用于你建议的方法会有什么缺陷。 你能给我一些关于你的思考过程的见解吗? :笑脸:

这是我会做出的改变:

pub enum TraitItemKind {
    // Generics aren't supported here yet
    Const(P<Ty>, Option<P<Expr>>),
    // `Generics` is already a field in `MethodSig`
    Method(MethodSig, Option<P<Block>>),
    // Added `Generics` here:
    Type(Generics, TyParamBounds, Option<P<Ty>>),
    Macro(Mac),
}

编辑: nikomatsakis 在 Gitter 上的回答

关于将它们放入类型的陷阱
我认为这也可以
我不愿意这样做的原因
是我们真的想对方法和类型做同样的事情(至少在理论上)
并且——正如我所说——原则上我认为我们没有理由不能对常量做同样的事情
我想如果你只是将泛型移动到类型变体
这可能行得通,但如果你看看,现在我们经常不得不做“类型/常量的一件事,方法的一件事”,正是因为它们是不同的
所以我怀疑代码会变得更加统一
老实说,我不确定它会如何发展=)——这可能会很痛苦
但是很多时候让事情变得比他们必须的更通用并不是那么糟糕,因为你可以插入 span_bug! 暂时在不可能的情况下调用(稍后我们会过来修补它们)

好的! 下一步是扩展解析器。 这里有一些提示。 让我们从特质项目开始。

此例程解析特征项。 我们希望扩展处理关联类型的案例以解析诸如type Foo<....> = ...;之类的东西(也可能是 where 子句)。 ( <...>是我们刚刚添加到 AST 中的“泛型”。)

目前它正在使用parse_ty_param ,它基本上解析诸如T: Foo之类的东西。我们将不得不停止这样做,因为关联类型声明的语法不再匹配类型参数的语法。 所以我们可能想要添加类似parse_trait_item_assoc_ty的东西。 这可以作为parse_ty_param()的一种克隆开始,但是我们将要修改它以在此处调用parse_generics() 。 如果存在一个泛型声明( <...> ),该例程将解析一个泛型声明,否则它只返回一个空泛型。 然后我们想在这里添加一个解析 where 子句的调用——您可以在解析方法时发生的调用上对其进行建模,注意结果存储在我们之前解析的generics中。

完成后,我们应该能够添加一些解析测试。 我会通过创建一个类似src/test/run-pass/rfc1598-generic-associated-types/的目录并添加您希望在其中成功解析的文件来做到这一点。 现在他们不能正常工作,但这没关系。 只需添加一个空的 main 函数。 然后我们还可以添加不应解析为src/test/ui/rfc1598-generic-associated-types/的示例(有关添加 UI 测试的说明,请参阅COMPILER_TESTS.md )。

其他的——我们需要在这一点上对这项工作进行特性门控,以避免人们在稳定的构建中使用这些东西。 这里有一些关于在 forge 上添加特性门的说明(参见最后一节)。 我们应该feature_gate.rs中为访问者添加visit_trait_itemvisit_impl_item #$ ; 如果该项目不是方法,但它具有非空泛型,我们可以调用gate_feature_post ( example )。

要设置名称解析,我认为我们所要做的就是获得适当的“肋骨”(名称解析的东西将范围内的名称集组织成肋骨;每个肋骨代表一个绑定级别)。 例如对于一个 impl:

impl<A,B> Foo<B> for Vec<A> {
   fn bar<T,U>(x: ...) { 
       for y in ... {
       }
   }
}

我们会有以下肋骨:

- <A,B> (from the impl)
   - <T,U> (from the `bar` method's generics)
      - `x` (from the parameter list)
          - `y` (from the let)

一般来说,对方法如何工作进行建模并不是一个坏主意。 我想我们也可以在这里做一些“未来的证明”。

这是将方法的类型参数带入范围的代码(这是针对在 trait 中定义的方法):

https://github.com/rust-lang/rust/blob/a35a3abcda67a729edbb7d649dbc663c6feabd4c/src/librustc_resolve/lib.rs#L1890 -L1892

而对于在 trait 中定义的type ,我们被硬编码为添加一个空类型参数 rib ( NoTypeParameters ):

https://github.com/rust-lang/rust/blob/a35a3abcda67a729edbb7d649dbc663c6feabd4c/src/librustc_resolve/lib.rs#L1897 -L1901

现在每个 trait/impl 项上都有泛型,我认为我们可能希望删除对type的处理并提取方法处理,以便它发生在更高级别。 对于没有泛型的项目(例如, const ),那么新引入的 rib 应该是空的,因此是无害的(我希望如此)。

其他兴趣点:

你明白了。

@petrochenkov——听起来对吗?

@nikomatsakis

听起来对吗?

一切看起来都是正确的。

下一步。 终生分辨率。

无论好坏,目前这是在与其他名称解析完全分开的代码中完成的。 这是因为它发生HIR 构建之后。 几乎可以肯定这会改变,但还没有改变。

不过,基本概念与普通名称解析中的相同,只是我们不称事物为“肋骨”,而是称其为“作用域”。 =)

由于这种“后期绑定”生命周期的概念,存在一些轻微的并发症。 不过,这在这里并不真正相关——泛型关联类型的所有生命周期都将是“早期绑定”,这是一种简单的情况。 “后期绑定”生命周期是在方法或函数上声明的生命周期,在调用该方法之前不提供其值。 我不会在这里详细介绍,因为它不那么相关——主要是我们不想遵循与其他类型的通用项目完全相同的方法模型,这与其他名称解析不同案例。

这是一个示例代码。 这是访问implstruct或其他非功能项的代码。 在这些情况下,就像在 GAT 中一样,我们基本上希望将所有生命周期参数从Generics带入作用域并将它们映射到“早期绑定”生命周期:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L370 -L388

你可以看到它首先创建了一个生命周期向量,为每个生命周期调用Region::early

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L376 -L378

接下来它创建一个Scopenext_early_index变量只是计算有多少早期绑定生命周期在范围内。 由于此代码特定于项目,因此将始终是在此当前项目上声明的早期绑定生命周期数。 (稍后我们将看看我们将额外的生命周期纳入范围的情况,这更符合 GAT 的要求。)

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L379 -L384

最后,我们调用with() 。 此方法将作用域带入作用域并调用闭包。 我们在这个闭包内访问的任何生命周期都将看到我们刚刚定义为在范围内的那些名称:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L385 -L388

好的,现在让我们再看一个例子。 这个案例涵盖了“impl traits”。 它所做的事情的细节并不那么重要(也就是说,你不必在impl Trait本身下进行脱糖)。 可以说它正在将一些新的早期绑定生命周期纳入范围——这正是我们想要为 GAT 做的事情。

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L482 -L501

让我强调一些事情。 首先,方法next_early_index返回下一个未分配的早期绑定索引:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L488

这提供了一个起点。 接下来,我们再次使用Region::early来创建新的早期绑定生命周期定义,这些定义将针对:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L490 -L492

最后,我们通过再次调用with将它们纳入范围:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L494 -L501

好的,这是两个例子。 我们将想做一些类似于第二个的事情。 我们要修改这两个方法的定义:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L509

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L520

对于关联类型,这两者都需要处理关联的泛型。 (在其他情况下,他们可能应该断言泛型是空的。)

所以我今天早些时候加入了这个。 我想确保我走在正确的轨道上:

  • 所有需要做的就是将生命周期引入地图,然后遍历 trait/impl 项? 检查似乎正在工作,虽然很难说(见下文)......可能只在简单(无界)的情况下工作。
  • 我在librustc_typeck/astconv.rs中删除了qpath_to_tyassociated_path_def_to_ty的类型参数禁止,以修复type parameters are not allowed on this type错误。 我认为这需要用一些检查来代替。 还...
  • 我现在从 typeck 收到崩溃。 (写回,具体而言)

typeck 失败被触发src/test/compile-fail/struct-path-associated-type.rs因为它为没有关联类型的值提供泛型。

如果我没看错,我至少需要添加一个检查关联的泛型计数是否匹配(试图找出在哪里执行此操作......),并且还可能进行其他检查以添加节点类型等。

将继续努力,但我是否会朝着正确的方向前进,我们将不胜感激。

嗨@brandonson! 我不愿意阻止任何人对 Rust 编译器进行黑客攻击,但我认为@sunjay已经在积极地对相同的东西进行黑客攻击,并且从一开始就一直在追求这种改变,所以他们完成可能是有意义的这种变化也是如此(我认为他们已经开始了)。 我不确定是否有一种明显的方法可以并行化这项工作(当然这是可能的,但我们必须提前完成这些步骤)。

但是,如果您有兴趣找到要解决的问题,我可以建议您解决这个里程碑上的一些错误吗? https://github.com/rust-lang/rust/issues/46472似乎没有人说,我可以尝试尽快在那里留下一些评论。

当然,我并不是要踩到任何人的脚趾,但我没有看到任何迹象表明实际上正在取得进一步的进展(尽管关于下一步的评论是相当近期的)。 真的,我开始尝试解决这个问题,因为在过去的几天里我多次想要 GAT,所以虽然我不一定介意将来处理一些 NLL 的东西,但这在我要解决的问题列表中更高眼下。

@sunjay ,如果您仍在积极工作/计划从事此工作,请务必告诉我 - 让我在此方面重复您的工作毫无意义。

@brandonson ,感谢您的热心和乐于助人的意愿。 :) 我确实在积极努力。 在与 Niko 密切合作以使其正确的同时,我一直在逐步完成实施的每个部分。

我会尽我所能尽快解决这个问题。 我也很想要这个功能!

进展如何? =) 迫不及待地想在每晚 <3 中尝试一下

我想知道 GAT 和 const 泛型的横截面将如何工作,以及它是否是放在一起的提案的一部分?

我的意思的一个例子:

trait Foo {
    type Bar<const N>;
}

@sunjay ,我认为这是一个非常重要的功能,在 2018 年路线图评论中被大量提及。 进展如何? 感谢您的工作!

这是我目前最想要的功能。 我希望这成为一个优先事项,并很快进入夜间!

所以最近我遇到了@sunjay ,他现在正忙于学校和其他事情,试图在这里制定下一步。 他们和我在某个时候会面并讨论了整体实施策略,最终在他们的 repo 上提交,我们留下了一堆内联评论

我认为最有意义的战略有两个方面:

  • 首先,我们需要编写更多的测试。
  • 我们的解析器和系统的其他一些“前端”位存在一些已知的缺陷,我们需要列举并修复这些缺陷。 我们可能会在测试中找到更多。
  • 到那时,我们已经准备好开始破解 trait 系统了:

    • 一些基础已经奠定,所以这主要是一个“重构”任务

    • 但我需要详细写下来,我知道的计划没有书面描述,我现在没有时间。

我将首先尝试编写一些指令 re: 测试,因为这些指令更容易执行,并在本周晚些时候安排一些时间来写下设计的其余部分将如何工作以及如何从它所在的位置获取代码现在到它需要的地方。

那好极了 !! 祝@sunjay在这方面和所有其他努力中好运。

第一步是确保我们拥有一整套测试。 现有的测试可以在以下位置找到:

src/test/ui/rfc1598-generic-associated-types

光看这些,我们已经可以看到一些需要完成的工作:

  • [ ] construct_with_other_type.rs -- 给出意外的 E0110 错误
  • [x] empty_generics -- 检查type Bar<,>是否给出错误,似乎没问题
  • [x] generic-associated-types-where.rs -- 检查我们是否可以在正确的位置解析where子句,似乎没问题
  • [ ] generic_associated_type_undeclared_lifetimes.rs -- 给出意外的 E0110 错误
  • [ ] iterable.rs -- 给出意外的 E0110 错误
  • [ ] pointer_family.rs -- 给出意外的 E0109 错误
  • [ ] streaming_iterator.rs -- 给出意外的 E0110 错误

缺乏覆盖的地方

  • [ ] 我们目前没有太多“预期用途”测试——即应该成功的事情

    • pointer_family似乎在那个方向

    • 我期待某种trait Iterable { type Item; type Iter<'a>: Iterator<Item = &'a Self::Item>; }测试

  • [ ] 生命周期阴影测试——我们通常禁止生命周期阴影,所以这些应该是非法的:

    • trait Foo<'a> { type Item<'a>; }

    • impl<'a> Foo<'a> for &'a u32 { type Item<'a> = i32; }

  • [ ] “完全合格”的语法似乎没有经过测试

    • 例如, pointer_family测试有Self::Pointer<T> ,但没有<Self as PointerFamily>::Pointer<T>

  • [ ] GAT 的参数数量错误。 例如,给定上面的Iterable定义:

    • <T as Iterable>::Item -- 根本没有参数? 坏的。

    • 请注意,我们可能会在某些情况下接受这一点,因为在某些可比较的情况下,我们允许省略生命周期。

      我可以去这里; 在这种情况下,我希望人们写一个明确的'_

    • <T as Iterable>::Item<'_> -- 正确!

    • <T as Iterable>::Item<T> -- 类型太多!

    • 等等,当然最好有类型和生命周期的测试

  • [ ] 关联类型的默认值? trait Foo { type Bar<T, U = T> where T: PartialEq<U>; }

    • 在这种情况下, SomeType::Bar<u32>将短于SomeType::Bar<u32,u32> ,我们应该检查一下。

修复意外的 E0110 错误

prohibit_type_params报告错误 E0110:

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L912

第一步是弄清楚从哪里调用它。 我首选的方法是获取本地构建并使用-Ztreat-err-as-bugRUST_BACKTRACE=1 $ 结合使用。 但我无法向您展示这些结果,因为 rustc 仍在构建中。 :P 所以我做了一个快速的rg prohibit_type_params ,让我快速指出我看到的一个可疑案例。

一次调用来自associated_path_def_to_ty

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L791 -L803

如注释所示,调用它来解析 $#$ Self::Pointer<T>之类的路径中的Pointer<T>组件(请注意,类型参数被视为路径段的一部分,以及它们所附加的名称)。 在 GAT 之前,类型参数在那里是不合法的(例如, T::Item ),所以我们只有一个全面的限制:

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L810

显然这是行不通的。 我们应该删除该行并用某种检查替换它,如果提供了参数,它们是否与预期的数字匹配。 为此,我们可能需要一些类似于create_substs_for_ast_path中的错误检查代码。 我们可能想在这里创建一些共享代码,特别是用于默认会计 - 也许我们实际上可以重用该功能?

有人还在做这个吗? 在我看来,这还有很长的路要走。 GAT 是我最想要的 RFC。 如果没有,我很乐意提供一些测试......

@rickyhan所以@Centril@gavento正在讨论关于划分测试工作的WG-traits gitter ,但我不知道是否取得了任何进展。 也许他们可以加入。我认为 PR 会受到欢迎。 =)

对不起,如果我是愚蠢的,但是这种事情对 GAT 来说是合法的吗?

trait Sequencer {
    type Wrap<A>;
    fn chain<A, B, F>(Self::Wrap<A>, F) -> Self::Wrap<B>
        where F: FnOnce(A) -> Self::Wrap<B>;
    fn wrap<A>(A) -> Self::Wrap<A>;
}

这是什么状态? 我知道粉笔最近获得了 gat 支持。 那是不是打算很快就生锈了?

@mark-im 我上周试了一下。 据我了解,语法解析器就在那里(尽管缺少测试)。 但是“实现”还没有写出来。 (有关详细信息,请参阅 https://github.com/rust-lang/rust/issues/44265#issuecomment-330915766)

@quadrupleslap AIUI,这将在以后成为可能,但起初,GAT 将仅支持生命周期参数..

@Boscop RFC 指定还将支持类型参数。

有人知道 rustc 中语法实现的确切状态吗? 似乎大部分都在那里,但排名较高的边界会生成 ICE:
http://play.rust-lang.org/?gist=a48959858ed5dd432c2396feae5c3cc1&version=nightly&mode=debug

我至少需要实现整个语法才能推进粉化工作。 如果有人仍在研究语法,请告诉我,否则我可能会自己去修复它:)

在我看来,沟通似乎是减缓 GAT 实施的主要问题。 似乎至少有几个人对此感兴趣,但没有人真正知道确切的实施状态以及谁已经在从事这项工作。 您如何看待Discord(或 IRC?)频道只是谈论实施 GAT? 我认为这肯定会有所帮助。

此外,我当然可以在接下来的几周内贡献几个工作日来解决这个问题(但我对编译器的这一部分还一无所知)。

所以我要求在 Discord 上建立一个专用频道。 但我没有得到一个频道,而是学到了一些东西。 我还搜索了与 GAT 相关的所有内容。 因此,我将尝试总结有关此功能的所有信息; 也许它对某些人有用。 但我什么都不知道,所以把这个和一粒盐一起吃吧! 另外:如果某些信息不正确,请告诉我,以便我修复它。

关于 GAT 的实施工作总结

从那时起,没有添加任何 UI 测试。 而且我找不到与 GAT 直接相关的任何其他 PR。

然而,上面的 (c) 点(特征系统)正在“秘密”进行。 据我了解,计划是尽快迁移到新的基于粉笔的特征求解器,而不是让 GAT 在旧系统上工作。 新特征求解器的集成正在通过“Chalkification”跟踪问题进行跟踪。 有不少关于粉笔和粉笔化的 PR。 值得注意的是,有一个名为“完成实施GAT”的粉笔 PR(合并于 2018 年 5 月 24 日)。 因此,GAT 的核心系统似乎已经到位。

也就是说,chalk 中的“GAT”是一个原型实现,在 rustc 中使用它不仅仅是use chalk; 。 正如@scalexm告诉我的那样:“似乎有很多[要做]”。


如需更多信息和帮助,查看#wg-traits Discord 频道和traits WG tracking issue可能很有用。

所以@nikomatsakis刚刚在 rust-lang discord 服务器上创建了#wg-traits-gat频道(在这里加入)。 如果我们能让所有想帮忙的人都进来,那就太好了。 再加上一些了解此功能状态的人(特别是还需要做什么以及我们可以在哪里提供帮助)。 这应该使沟通更容易和更快,特别是对于像我这样尚未深入参与/部分特质 WG 的人:)

最近有这方面的更新吗? 我们是否在等待 Chalk 集成到编译器中? (也许有一个单独的问题。)

也许有一个单独的问题。

48049

正如一个注释(可能已知),这段代码使编译器恐慌:

use typenum::{U1,U2,U3,Unsigned};

trait Array {
    type Of<Elem> ;
}

impl Array for U1 { type Of<T> = [T;1]; }
impl Array for U2 { type Of<T> = [T;2]; }
impl Array for U3 { type Of<T> = [T;3]; }

@varkor太棒了,谢谢!

另一个注意事项(同样,可能是已知的)。 有了 GAT,我们将能够很好地抽象&&mut类型,因此我们可以停止在函数my_funcmy_func_mut之间不断复制粘贴代码

#![feature(arbitrary_self_types)]
#![feature(generic_associated_types)]

use std::marker::PhantomData;

struct Pure <'t> (PhantomData<&'t()>);
struct Mut  <'t> (PhantomData<&'t()>);

type Ref<M, T> = <M as Acc>::Pat<T>;

trait Acc { type Pat<T: ?Sized>; }
impl<'t> Acc for Pure <'t> { type Pat<T> = PureRef <'t, T>; }
impl<'t> Acc for Mut  <'t> { type Pat<T> = MutRef  <'t, T>; }

struct PureRef <'t, T: ?Sized> (&'t     T);
struct MutRef  <'t, T: ?Sized> (&'t mut T);


/// USAGE ///

struct Buf<T> {
    data: Vec<T>
}

impl<T> Buf<T> {
    fn as_mut<M: Acc>(s: Ref<M, Self>) -> Ref<M, [f32]>
    {
        unimplemented!()
    }
}

几乎可以编译。 我们也可以在没有 GAT 的情况下实现它,但类型会爆炸:

#![feature(arbitrary_self_types)]

use std::marker::PhantomData;
use std::ops::Deref;

struct Pure <'t> (PhantomData<&'t()>);
struct Mut  <'t> (PhantomData<&'t()>);

type Ref<M, T> = <M as Acc<T>>::Pat;

trait Acc<T: ?Sized> { type Pat; }
impl<'t, T: 't + ?Sized> Acc<T> for Pure <'t> { type Pat = PureRef <'t, T>; }
impl<'t, T: 't + ?Sized> Acc<T> for Mut  <'t> { type Pat = MutRef  <'t, T>; }

struct PureRef <'t, T: ?Sized> (&'t     T);
struct MutRef  <'t, T: ?Sized> (&'t mut T);


/// USAGE ///

struct Buf<T> {
    data: Vec<T>
}

impl<T> Buf<T> {
    fn as_mut<M>(self: Ref<M, Self>) -> Ref<M, [f32]>
    where M: Acc<Self> + Acc<[f32]>,
          Ref<M, Self>: Deref<Target = Self>
    {
        unimplemented!()
    }
}

(这实际上是编译)

也许这更像是一个支持问题,但我认为这可能对那些来到这个页面的人理解这个提议很有用,因为我从RFC或这里的评论中并不完全清楚:

使用 1.41 nightly,我尝试了以下方法:

pub trait MyTrait {
    type MyType<U>;

    fn f<U>(self, x : <Self as MyTrait>::MyType<U>);
}

这无法编译,错误是“不允许类型参数” MyType

然后我删除了<U> ,这看起来很可疑,但我想我会试一试:

pub trait MyTrait {
    type MyType<U>;

    fn f<U>(self, x : <Self as MyTrait>::MyType);
}

令人惊讶的是编译。 但是当我写一个 impl 时:

impl MyTrait for u64 {
    type MyType<U> = U;

    fn f<U>(self, x : <Self as MyTrait>::MyType) -> <Self as MyTrait>::MyType {
        x;
    }
}

这让编译器感到恐慌。

我的问题是:

  1. 这种方案现在可行,但我做错了吗?
  2. 如果现在不可能,那么在这个提议下是否有可能?
  3. 如果是这样,是否有任何关于我可以在夜间执行此操作的时间表的想法?

提前感谢您抽出宝贵时间回答这个问题。

@clintonmead我相信这最终应该是可能的,但是功能实现还没有完成(实际上,编译器会警告您,当您尝试使用它时它可能会崩溃!)。 我不知道时间线是什么。

也许值得与 Chalk 的家伙一起检查一下集成是否足够先进,可以恢复此功能的工作?

这现在被阻止了

  • #30472
  • #67509
  • #67510
  • #67512
  • #67513

我们可以使用当前的阻止程序更新问题描述吗?

添加了@DutchGhost提出的更多阻塞问题

这将是模拟高级类型的重要一步。

我在想象这样的事情:

// the plug/unplug idea is from https://gist.github.com/edmundsmith/855fcf0cb35dd467c29a9350481f0ecf

trait Monad /* : Applicative (for pure/return, doesn't matter for this example) */ {
    // Self is like the "f a" in haskell

    /// extract the "a" from "f a"
    type Unplug;

    /// exchange the "a" in "f a" in the type of Self with B
    type Plug<B>: Monad;

    fn bind<B, F>(this: Self, f: F) -> Self::Plug<B>
    where
        F: Fn(Self::Unplug) -> Self::Plug<B>;
}

impl<A> Monad for Option<A> {
    type Unplug = A;
    type Plug<B> = Option<B>;
    fn bind<B, F>(this: Self, f: F) -> Option<B>
    where
        F: Fn(A) -> Option<B> {
        this.and_then(f)
    }
}

关于拟议实施的问题:

我有一个看起来像这样的特征

trait TradeableResource{
}

和一个看起来像这样的实现者

struct Food(f64);
impl TradeableResource for Food{}

我希望能够将我的特征的所有实现者限制为“新类型”(包装 f64 的单元素元组结构)。

在这里考虑提议的实施是否可行?

诚然,以下内容看起来有点奇怪,但希望能展示我想要做的事情。

trait TradeableResource{
    type Wrapper<T>:T(f64)
}

@ChechyLevas据我所知,这不在 GADT 的范围内。

但是你想要做的事情并不需要 GADTs 开始,据我所知,如果你愿意放弃它是一种新类型的保证,而是必须分别包装和检索内部值的函数. 它不那么方便,但应该可以很好地工作。

因此,您可以执行以下操作:

trait Traceable resource: From<f64> + Into<f64> { }

(这将要求您还可以在两个方向上实现From ,我将通过宏来实现)

我想对目前的状态有一点感觉。 我一直在玩异步特征和生命周期。
我想做的一件事是创建一个具有关联ReadAt: Future类型的trait ReadAt<'r> 。 这适用于很多情况,除非我想使用特征对象。

主要是因为执行以下操作会迫使我将一个不够通用的生命周期附加到R

impl<'a, 'r, R> ReadAt<'r> for &'a dyn for<'z> ReadAt<'z, ReadAt = R> + 'a {
    type ReadAt = R; // cannot have an `impl<R<'lifetime>>`
    ...
}

所以我想,也许这可以用 GAT 解决,但我遇到了类似的问题,我需要再次使用一些语法魔法,因为 trait 对象需要写出关联的类型:

喜欢:

trait ReadAt {
    type ReadAt<'r>: Future<Output = io::Result<usize>>;

    fn read_at<'a>(&'a self, buf: &'a mut [u8], at: u64) -> Self::ReadAt<'a>;
}

impl<'d> ReadAt for &'d (dyn ReadAt<HERE> + 'd) {
}

我需要一种方法将生命周期包含在HERE中。 例如,这是可以接受的,但 IMO 还不够,因为R太具体了:

impl<'d, R> ReadAt for &'d (dyn ReadAt<ReadAt = R> + 'd) {
    type ReadAt<'r> = R;

    fn read_at<'a>(&'a self, buf: &'a mut [u8], at: u64) -> Self::ReadAt<'a> {
        (**self).read_at(buf, at)
    }
}

但是我无法真正测试这是否会起作用,因为我不确定如何将其转换为特征对象,因为我不知道如何将生命周期转换为以下代码段:

struct Test<T: ReadAt>(T);

impl<T: ReadAt> Test<T> {
    fn into_trait_object<'a>(&'a self) -> Test<&'a dyn ReadAt<ReadAt = T::ReadAt>> {
        todo!();
    }
}

由于T::ReadAt需要我无法提供的生命周期,因此这将错过dyn ReadAt<ReadAt<'r> = T::ReadAt<'r>>的语法扩展。 (或者为了匹配IMO的生命周期参数,上面的代码片段可以正常工作;-))

由于我只使用生命周期参数,而不是类型参数,我认为这在技术上应该是可行的,除非生命周期参数会以某种方式影响 vtables 和类型大小?

@jendrikw您的代码使用最新的 nightly(1.46) 编译和运行。 我用自定义类型等做了一些测试。但我缺乏 fp 知识来检查更多。

今天,我尝试将 GAT 用于我认为一生可能不够通用的事情(工作代码如下;完整的代码文件):

/// Helper trait for "stripping indention" from an object reference
pub trait AsUnindented<'ast> {
    type Output;

    /// Returns a reference to the unindented part of `Self`
    fn as_unindented(&'ast self) -> Self::Output;
}

impl<'ast, T: 'ast> AsUnindented<'ast> for Indented<T> {
    type Output = &'ast T;

    #[inline]
    fn as_unindented(&'ast self) -> &'ast T {
        &self.data
    }
}

impl<'ast, T: 'ast> AsUnindented<'ast> for crate::Block<T>
where
    T: AsUnindented<'ast> + 'ast,
{
    type Output = crate::View<'ast, T, fn(&'ast T) -> T::Output>;

    #[inline]
    fn as_unindented(&'ast self) -> Self::Output {
        crate::View::new(self, T::as_unindented)
    }
}

然后,我尝试在以下代码中使用 GAT:

pub trait AsUnindented {
    type Output<'ast>;

    /// Returns a reference to the unindented part of `Self`
    fn as_unindented<'ast>(&'ast self) -> Self::Output<'ast>;
}

impl<T> AsUnindented for Indented<T> {
    type Output<'ast> = &'ast T;

    #[inline]
    fn as_unindented<'ast>(&'ast self) -> &'ast T {
        &self.data
    }
}

impl<T> AsUnindented for crate::Block<T>
where
    T: AsUnindented,
{
    type Output<'ast> = crate::View<'ast, T, fn(&'ast T) -> T::Output<'ast>>;

    #[inline]
    fn as_unindented<'ast>(&'ast self) -> Self::Output<'ast> {
        crate::View::new(self, T::as_unindented)
    }
}

那是行不通的。 对于两个 trait 实现,它都以E0309 ( the parameter type 'T' may not live long enough ) 失败。 我不确定如何解决这个问题,或者我是否有一些误解。 编译器希望我在impl级别对T附加一个界限,但在那个级别,没有生命周期'ast ,我不想限制T: 'static (像for<'ast> T: 'ast这样的东西不起作用)。

编辑:我尝试将 GAT 应用到我的代码库的另一部分,它只抛出一个错误(目前),但不能简单地修复它,因为它取决于我前面提到的问题的修复。

可以将绑定的寿命添加到关联类型(在特征版本上使用Self: 'ast )。 这个测试显示了它应该是什么样子:
https://github.com/rust-lang/rust/blob/db4826dd6ca48663a0b4c5ab0681258999017c7d/src/test/ui/generic-associated-types/iterable.rs#L6 -L21

好吧,这只是部分工作......


错误信息

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:33:5
   |
33 |     type Output<'ast> where T: 'ast = &'ast T;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:45:5
   |
45 |     type Output<'ast> where T: 'ast = crate::View<'ast, T, fn(&'ast T) -> T::Output<'ast>>;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:59:5
   |
59 | /     type Output<'ast2>
60 | |         where
61 | |             T: 'ast2,
62 | |             O: 'ast2
63 | |         = crate::View<'ast2, T, crate::view::MapViewFn<F, fn(F::Output<'ast2>) -> O::Output<'ast2>>>;
   | |_____________________________________________________________________________________________________^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast2`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `O` may not live long enough
  --> src/indention.rs:59:5
   |
59 | /     type Output<'ast2>
60 | |         where
61 | |             T: 'ast2,
62 | |             O: 'ast2
63 | |         = crate::View<'ast2, T, crate::view::MapViewFn<F, fn(F::Output<'ast2>) -> O::Output<'ast2>>>;
   | |_____________________________________________________________________________________________________^
   |
   = help: consider adding an explicit lifetime bound `O: 'ast2`...
   = note: ...so that the type `O` will meet its required lifetime bounds

似乎代码通过了一个阶段(以前,它出现了类似的错误,但在它们之间,出现了另一类错误,仅包含E0107 s)hm。

编辑:我错过了第一条语句( where Self: 'ast )。 那部分现在已经修好了。 我试着继续。


附加上下文

好的,以下代码段:

impl<'ast, T, F, O> AsUnindented for crate::View<'ast, T, F>
where
    Self: Clone,
    F: crate::view::ViewFn<T, Output = &'ast O>,
    O: AsUnindented + 'ast,
{
    type Output<'ast2>
        where
            T: 'ast2,
        = crate::View<'ast, T, crate::view::MapViewFn<F, fn(&'ast O) -> O::Output<'ast>>>;

    #[inline]
    fn as_unindented<'ast2>(&'ast2 self) -> Self::Output<'ast2> {
        self.clone().map::<for<'x> fn(&'x O) -> O::Output<'x>, _>(O::as_unindented)
    }
}

错误:

error[E0631]: type mismatch in function arguments
  --> src/indention.rs:66:67
   |
66 |         self.clone().map::<for<'x> fn(&'x O) -> O::Output<'x>, _>(O::as_unindented)
   |                                                                   ^^^^^^^^^^^^^^^^
   |                                                                   |
   |                                                                   expected signature of `for<'x> fn(<F as view::ViewFn<T>>::Output<'x>) -> _`
   |                                                                   found signature of `for<'x> fn(&'x O) -> _`

而且我知道<F as view::ViewFn<T>>::Output<'x> ==> &'ast O&'x O不相等,但我不知道如何修复它(编译器不接受绑定的F: for<'x> crate::view::ViewFn<T, Output = &'x O> )。
其他尝试错误:

error[E0582]: binding for associated type `Output` references lifetime `'x`, which does not appear in the trait input types
  --> src/indention.rs:56:39
   |
56 |     F: for<'x> crate::view::ViewFn<T, Output = &'x O>,
   |                                       ^^^^^^^^^^^^^^

我不知道如何在特征输出类型赋值中表达forall<'x>绑定,例如

where
    F: crate::view::ViewFn<T, for<'x> Output<'x> = &'x O>,

甚至不解析(“预期+,::>之一,发现= ”)。

67510 磁道能够写入该边界。 该示例可能还需要惰性规范化 (#60471)。

self.clone().map的 trait 输出类型真的是O::as_unindented的类型,即 map 的参数类型吗
不知道crate::View是什么,但map函数可能需要不同的参数,因此类型不匹配。

@sighoya我在之前的帖子中链接了存储库,这里是crate::view::ViewFn::map的 impl 的直接链接

编译器对此有何评论:

where
    for<'x> F: crate::view::ViewFn<T, Output<'x> = &'x O>,

它不解析(还)。

@matthewjasper

Rust Nomicon ,以下解析:

where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,

是不是因为使用了Output<'x> = &'x 0>才没有解析?

是的, Output<'x>=...不会在那个位置解析。

我有一个 create, grdf ,它不再用 rustc 1.46.0-nightly编译。 GAT 最近有什么变化吗?

我的情况有点奇怪,因为我必须首先使用一个技巧来使其工作。 我必须有一个具有关联迭代器类型的Graph特征。 为了使它工作,我将所有迭代器放在另一个特征中, Iter

pub trait Iter<'a, T: 'a> {
    type Triples: Iterator<Item = Triple<&'a T>>;
    type Subjects: Iterator<Item = (&'a T, Self::Predicates)>;
    type Predicates: Iterator<Item = (&'a T, Self::Objects)>;
    type Objects: Iterator<Item = &'a T>;
}

pub trait Graph<T = crate::Term> {
    /// Iterators.
    type Iter<'a>: Iter<'a, T>;

    ...
}

我有一个Graph的实现,到目前为止效果很好:

impl<'a, T: 'a + Hash + Eq> crate::Iter<'a, T> for Iterators {
    type Objects = Objects<'a, T>;
    type Predicates = Predicates<'a, T>;
    type Subjects = Subjects<'a, T>;
    type Triples = Iter<'a, T>;
}

impl<T: Hash + Eq> crate::Graph<T> for HashGraph<T> {
    type Iter<'a> = Iterators;

    ...
}

现在我已经更新了 rustc 它失败了以下内容:

error[E0309]: the parameter type `T` may not live long enough
  --> src/hash_dataset.rs:50:2
   |
50 |     type Iter<'a> = Iterators;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'a`...
   = note: ...so that the type `T` will meet its required lifetime bounds

这对我来说没有多大意义,因为T已经被'a in Iter ...

好吧,我通过添加一些where边界使它再次工作。
在性状中:

type Iter<'a>: Iter<'a, T> where T: 'a;

并在实施中:

type Iter<'a> where T: 'a = Iterators;

但我不确定理解where边界的确切语义,特别是在实现中(我第一次看到)。 还有为什么它以前有效。

编辑:我什至能够删除我讨厌的黑客并将相关的迭代器放入特征本身。

添加了@DutchGhost 提出的更多阻塞问题

您也可以添加 https://github.com/rust-lang/rust/issues/74684 :)

我不喜欢请快点发帖,但是这个功能已经停滞了将近三年,我相信它是最想要的新功能之一。

我们在哪里? 此处的最新状态摘要是@LukasKalbertodt2018 年 6 月发布的。 是在等待“粉化”吗?

天真的观察:几乎所有我想要的 GAT 用途需要一个生命周期参数。 一个缩减版的 GAT 会更容易交付吗?

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 是一个(有点简洁的)更新。

67510 是最后一个需要实现的主要 ICE/缺失功能。

这个 RFC 会直接使MonadFunctor成为可能吗? 还是在 HKT 上需要做更多的工作?

这个 RFC 会让 Monad 和 Functor 直接成为可能吗? 还是在 HKT 上需要做更多的工作?

@ibraheemdev RFC 声明

当人们谈论更高种类的类型时,这并没有添加人们想要的所有功能。 例如,它不启用像 Monad 这样的特性。 有些人可能更喜欢同时实现所有这些功能。 但是,此功能与其他种类的高级多态性向前兼容,并且不排除以任何方式实现它们。 事实上,它通过解决一些会影响其他类型的更高种类的实现细节(例如部分应用)来铺平道路。

@ibraheemdev :我的感觉是,使 monad 和 functor 成为可能的最惯用方法是引入泛型关联特征,但泛型关联类型肯定是先决条件。

是否有不需要 GAT 的Monad路径?

@ibraheemdev假设是的,可以直接实现 HKT,然后在顶部实现Monad特征。 然而,在这个方向上没有工作发生,而且可能永远不会发生,因为这种方法并不能真正解决 Rust 的问题: https ://twitter.com/withoutboats/status/1027702531361857536

也许我错了,但我认为 GAT 允许类似

trait MonadFamily {
    type Monad<T>;
    fn pure<T>(inner: T) -> Self::Monad<T>;
    fn bind<T, U, F: FnOnce(T) -> U>(this: Self::Monad<T>, f: F) -> Self::Monad<U>;
}

@ibraheemdev

是否有不需要 GAT 的Monad路径?

是的,请参阅Rust 中模拟高级类型的方法。 它现在甚至可以稳定运行。

此页面是否有帮助?
0 / 5 - 0 等级