Rust: “ Macros 1.1”(RFC#1681)的跟踪问题

创建于 2016-08-22  ·  268评论  ·  资料来源: rust-lang/rust

rust-lang / rfcs#1681的跟踪问题。

cc @alexcrichton

稳定待办事项

石蕊测试:

特征:

  • 板条箱名称,当前为proc_macro
  • 板条箱类型,当前proc-macro
  • #[proc_macro_derive(Foo)]属性
  • 使用-L#[macro_use]加载proc-macro箱来加载它们
  • 遮挡是一个错误
  • 没有卫生
  • 为整个结构传递令牌流并全部接收回来
  • 货物清单属性,当前proc-macro = true

已知错误:

  • []-恐慌派生可能跨度错误-#36935
  • []-带有mod foo令牌流失败-#36691
  • []-文档未以proc_macro -#38749的价格发布
  • [x]-多种模式的自定义属性很难-https : //github.com/rust-lang/rust/issues/35900#issuecomment -252499766
  • [x]-proc宏库的货物测试失败-#37480
  • [x]-顺序仍然很重要-https : //github.com/rust-lang/rust/issues/35900#issuecomment -252430957(由https://github.com/rust-lang/rust/pull/37067修复)
  • [x]-无法记录rustc-macro板条箱-https: //github.com/rust-lang/rust/issues/36820 (已在#36847中修复)
  • [x]-货运过于频繁-https: //github.com/rust-lang/rust/issues/36625 (已在https://github.com/rust-lang/rust/pull/36776中修复)
  • [x]-编译器生成的属性使自定义派生作者的生活变得困难-https : //github.com/rust-lang/rust/issues/35900#issuecomment -245978831

    • [x]-将扩展顺序更改为用户优先-https : //github.com/rust-lang/rust/issues/35900#issuecomment -246840357(已在https://github.com/rust-lang/rust中修复/ pull / 36782)

      实施计划

  • [x]-创建一个rustc_macro板箱

    • [x]-具有librustc_macro链接到libsyntax 。 依赖于librustc_macrolibrustc_driver
    • [x]-使用我们的标准标头将rustc_macro标记为不稳定。
    • [x]-仅用rustc_macro标记#![crate_type = "rlib"] ,不产生dylib。
    • [x]-在内部使用libsyntaxTokenStream实现rustc_macro的API
    • [x]-用TokenStream lang项标记rustc_macro ,以便编译器知道。
  • [x]-添加rustc_macro_derive属性

    • [x]-验证其格式完全相同foo(bar) ,没有其他参数/​​格式

    • [x]-验证它仅适用于功能

    • [x]-验证它仅适用于根模块中的功能

    • [x]-使用上面添加的TokenStream lang项目验证签名

    • [x]-将带有rustc_macro_derive所有功能符号及其使用的派生模式编码为元数据。

  • [x]-为其他箱子添加rustc-macro箱子类型

    • [x]-将其连接起来以产生dylib

    • [x]-确保dylib获取元数据

    • [x]-确保不能将rustc-macro链接为dylib

    • [x]-确保除标记有#[rustc_macro_derive]物品外没有_reachable_物品

    • [x]-添加cfg(rustc_macro)作为不稳定的cfg指令,将其设置为rustc-macro板条箱类型

    • [x]-确保rustc-macro箱动态链接到libsytnax

  • [x]-为rustc-macro箱填写#[macro_use]支持

    • [x]-扩展库加载程序以查找rustc-macro箱,与今天在查找条板箱时跟踪的dylib / rlib分开

    • [x]-解析rustc-macro箱的元数据,以了解符号/派生模式配对

    • [x]- dlopen dylib

    • [x]-如果任何派生模式会影响其他任何模式,则会产生错误。

  • [x]-添加货物整数

    • [x]-识别与rustc-macro类似的plugin = true

    • [x]-根据情况传递--crate-type=rustc-macro

    • [x]-为rustc-macro板条箱查看与插件板条箱相同的主机/目标逻辑(例如,始终为主机编译rustc-macro板条箱)

  • [x]-测试

    • [x]-加载rustc-macro的虚拟测试,虚拟#[derive]特性

    • [x]-名称冲突是一个错误

    • [x]-为错误的架构进行编译是错误的

    • [x]-无法发出板条箱类型rustc-macro,其他任何东西(例如rustc-macro + dylib)都是错误

    • [x]-跨度信息不可怕

    • [x]-从结构以及字段中删除属性

    • [x]-在结构旁边添加impls

    • [x]-交叉编译查找主机rustc-macro crate类型

    • [x]-不要将香草dylib加载为rustc-macro板条箱类型

    • [x]-在rustc-macro板条箱中不能有超出宏派生函数的公共出口

    • [x]-派生宏必须具有必需的签名

    • [x]-在一次编译中加载两个宏板条箱

B-RFC-implemented B-unstable T-lang final-comment-period

最有用的评论

好的,我今天要调查一下,看看能走多远。

所有268条评论

我已经用需要做的检查清单更新了问题描述。 它可能并不详尽,但它有望使我们达到目标的90%

好的,我今天要调查一下,看看能走多远。

从#35957开始:我们应该更多地使用librustc_macro板条箱的名称。 特别是,这打算成为一个长期存在的板条箱,其中包含所有宏作者的必需品,因此将rustc_macro(至少在我看来是这样)限制为1.1想法似乎很糟糕。 我以前曾想为此使用libmacro,但是由于macro是一个保留字(将来我们可能希望将其作为关键字),所以这是不可能的。 @cgswords和我一直在使用libproc_macro,我认为这不是一个坏名字,尽管我不是100%满意。

@nrc :好的,我对命名的直接想法是:

  • extern crate macros; -简短而有趣,但可能被视为包含宏,而不是用于编写宏的支持代码
  • extern crate macro_runtime; -锡罐上的字眼
  • extern crate metarust; -为Rust编写Rust,以便在Rust上操作
  • extern crate bikeshed; -使用程序宏,您可以使用任何颜色的Rust!
  • extern crate macrame; -听起来像是“ macro make [r]”; 最好在原始库上保留一个将来的“不错”的API。

@nrc这个问题的一个重要方面似乎是我们各种宏样式的整体命名-尤其是,如果我们使用libproc_macro ,我们将对“过程宏”做出坚定的承诺。术语。 我在这里没有很强的见解,但是我不确定我们是否已经公开探索术语空间。

明确地说,您是否认为今天的macro_rules只是“宏”,即我们必须使用“过程宏”来限定宏的默认含义? 对我来说,这似乎是一个非常合理的计划。 在那个世界上,我认为libproc_macrolibmacros更好。

我在这里的想法是所有宏都是“宏”,当我们需要根据实现进行区分(所有类型的用法应完全相同)时,我们使用“过程宏”与“示例宏”。 我想完全消除“语法扩展”和“编译器插件”(有一天将后者用于实际插件)。

但是,是的,我强烈希望落后于“过程宏”术语。

@nrc对我来说很有道理! 虽然“示例宏”有点笨拙,但这也是一个非常令人回味/直觉的术语。 我对“过程宏”的担心是,“过程”在Rust中不是一回事。 我想知道是否有一种使连接更直接的方法。 “函数宏”不是很正确,但是也许可以使您理解我的意思?

是的,它不是很完美,但是鉴于它是Rust以外的一个著名/使用过的术语,我认为这比创造我们自己的术语更好。 可以使用“程序宏”,但我更喜欢“过程宏”。

Perl与“程序宏”最接近的等效术语是“源过滤器”,(特别是从AST到令牌的转换)是一个非常合适的描述。

也许“语法转换器”或“程序宏”可以很好地用作名称? 但是我对程序宏没有任何问题。

人们已经把“程序性”称为“程序性”,我认为它已经清楚地理解了它的含义,特别是与“示例宏”相反。 我不会担心尝试查找其他名称。

我喜欢经常使用的术语“过程宏”(或者也许是“自定义宏”)。 我特别喜欢使用_macro_一词,因此很明显,它们可以(最终...)以与“常规宏”相同的方式使用。 出于这个原因,我不喜欢“源过滤器”(我也希望过滤器只是丢弃数据,而不转换数据,尽管我知道这两个术语都使用了)。

我可以选择libproc_macrolibmacros 。 我喜欢后者,只是因为我不喜欢在可以轻易避免的情况下在板条箱名称中使用_ 。 =)

一个问题:我们是否曾希望有可以在非过程宏中使用的“支持例程”? 我不知道这样的计划,但是如果我们这样做了,并且我们希望它们在同一个箱子中,那么libmacros将是一个更好的名字。 =)

(例如,我正在考虑急切扩展RFC中的@dherman的评论。)

@nikomatsakis :一个相关但细微不同的问题是我在https://github.com/rust-lang/rfcs/pull/1561#discussion_r60459479中提出的用例-我们是否希望过程宏的实现函数能够调用其他程序宏的实现功能?

我可以很容易地看到想要允许一个自定义派生调用另一个自定义派生,这本质上将使过程宏定义本身能够用作此类“支持例程”

但是,是的,我认为@dhermangensym示例非常引人注目。 当然,如果上述问题的答案是“是”,那么gensym既是宏(例如,可以由宏使用),又是支持功能(可以由过程宏使用)。

我有一个货物PR, https://github.com/rust-lang/cargo/pull/3064 ,它应该检查清单中的所有“货物集成”框。

我在货物PR上发表了评论,但我认为我们需要不同类型的_dependency_,而不仅仅是不同类型的_package_。 首先,我认为从美学和人体工程学角度来看这只是更好,但这只是我的观点。 但是我也有两个具体原因。

  • 在拥有公共和私人部门的未来中,重要的是要知道,要知道程序部门的公共部门和常规公共部门的部门不必重合。 例如
  • 在将来使用内联过程宏/准引用时,可以在编译时使用任何库。
  • >我们是否希望过程宏的实现函数能够调用其他过程宏的实现函数?

如上所示,同一条板条箱可以是另一个程序宏条板箱的常规部,也可以是另一条板条箱的宏部。

Serde工作吗?

https://github.com/serde-rs/serde/releases/tag/v0.8.6

(在某些情况下,除了容器属性#36211)

太棒了,谢谢@dtolnay的更新!

这些宏上有文档吗? 我想,使用它们的唯一示例是Serde,对吗?

好的货物已经降落了。 很好,但是在稳定之前的某个时间重新访问https://github.com/rust-lang/rust/issues/35900#issuecomment -243976887会很好。 [出于它的价值,我打算在原始RFC中提出,但忘了。]

我认为“示例宏”和“过程宏”可以分别更好地分类为“声明性宏”和“命令性宏”。 这与更广为人知的编程语言的声明式/命令式分类提供了信息上的相似之处。 由于命令式被视为过程的超集或同义词,对于习惯于“过程宏”术语的人们来说,它应该足够接近。 它还应避免与生锈本身中的过程/功能/方法概念混淆。
这种命名方案为我们提供了一个macro_imp板箱和一个与macro_rules平行的模块。 macro_rules最终可能会成为更通用的macro_dec板条箱的模块。

@nrc ,当您提到“实际插件”时,您包括metacollectclippy之类的东西,诸如rustwrustfmtRust语言服务器之类的东西,还是其他程序类别?

对于它的价值,我有点不喜欢“ by example”这个名字(因为在我看来, $foo模式不是“ examples”)。 声明式和命令式听起来对我来说更好。

玩这个我注意到一个问题。 看起来Rust提供的派生有时会添加编译器知道忽略的属性。 通过自定义派生时,此上下文会丢失。 我希望作为自定义派生给出的identify函数是无操作的,但是它将导致添加#[structural_match]属性周围的错误。


复制脚本

(在名为demo_plugin的箱子中)

#![feature(rustc_macro, rustc_macro_lib)]

extern crate rustc_macro;

use rustc_macro::TokenStream;

#[rustc_macro_derive(Foo)]
pub fn derive_foo(input: TokenStream) -> TokenStream {
    input
}

(在另一个箱子中)

#![feature(rustc_macro)]

#[macro_use] extern crate demo_plugin;

#[derive(PartialEq, Eq, Foo)]
struct Bar {
    a: i32,
    b: i32,
}

删除#[derive(Eq)]会使一切正常。

@sgrif啊,是的,的确的确感谢您提醒我!

因此,这里发生了一些事情:

  • 使用#[derive(PartialEq, Eq)] ,编译器会静默添加#[structural_match]因为它静态地了解PartialEq派生确实确实满足了该约定。
  • 我相信#[derive(Copy, Clone)]加上#[rustc_copy_clone_marker]也会产生类似的属性。
  • 当前,这是可行的,因为这些属性的范围表示“允许内部不稳定”。 但是,在解析和重新解析时,我们会丢失范围信息。

因此,我们可以执行一些解决方案:

  • 按照惯例,自定义派生是第一位的,例如#[derive(Foo, Eq, PartialEq)]
  • 依靠自定义派生来忽略这些属性
  • 如果我们检测到自定义派生,请不要发出自定义属性
  • 在编译器中使用完全不同的机制来传达这些属性的含义,而不是通过AST本身。

我赞成不发出这些属性或使用其他机制。 但是,这确实很棘手,因为#[derive(Copy, Foo, Clone)]也需要工作(自定义代码在中间运行,可能会更改定义)。

我的首选做法是,如果我们检测到自定义派生,就不要发出这些属性。 即使那样,这可能也不是小事。 就目前而言,“自定义至上和标准至上”的约定就足够了,但是我认为我们应该在稳定之前解决此问题。

免责声明:我只对编译器有一个外部看法-因此有很多我不知道的地方。 ^^

但是据我了解,宏1.1自定义派生的当前方法更像是一种临时解决方法。 临时解决方法可能会转化为“一段时间”。 但是从长远来看(=宏2.0),我们将不再进行字符串解析往返,这当前会导致span信息丢失。 我想知道AST中的这些隐藏属性在宏2.0的世界中是否是一件坏事? 也许对编译器有更多了解的人可以说出来。 如果这些隐藏的属性在将来的世界中确实有意义,那么我会争先解决,方法是按惯例将定制派生放在前面。 整个事情已经是一种解决方法了,不是吗?

@ colin-kiegel我认为您是对的,因为我们现在正在做的事情是_not_面向未来的。 假设您有,例如:

#[derive(Eq, Foo, PartialEq)]

今天,我们将添加Eq实现,然后为Foo运行自定义代码,然后添加PartialEq 。 结构可以之间_change_ EqPartialEq ,所以#[structural_match]通过加入Eq实际上可能没有后正确Foo运行。

从这个意义上说,我同意它们在任何情况下都不一定能面向未来!

我的感觉是,依赖于结构更改的自定义派生通常很难很好地构成,而与那些隐藏的属性无关。 v2.0自定义派生工具能够更改商品的结构,还是会以某种方式仅限于装饰?

是的,尽管我怀疑#[derive]任何合理实现都会保留原始结构的结构,但我相信该功能将永远存在(在TokenStream-> TokenStream接口中固有)。

我不认为我们可以要求输出令牌流不包含该结构本身,以确保该结构不会被更改? 输入的struct TokenStream将是不可变的前缀吗? 最大的问题是确保在构建完成后忽略插件正在使用的无法识别的属性。 也许每个#[derive()]都可以有一个前缀(例如#[derive(Foo)]带有前缀Foo_),他们必须理解这些属性的开头,然后在处理每个自定义后派生出这些属性?

@mystor是的,这种方法的问题是无法识别的属性,这就是为什么我们将整个结构作为输入。 通常,这比依赖于特定的前缀/后缀/注册/等更为灵活。

如果v2.0自定义派生可以将自定义属性标记为_used_,则可以将其限制为对其余项令牌流的_read_only_访问。 这样可以确保IMO更好地组合自定义衍生产品。 如果v2.0宏需要更改项目的结构,则必须使用其他api,而不是自定义派生。 这样, #[structural_mach]和(自定义)派生顺序的问题将仅出现在宏1.1中。 那有道理吗?

另一个问题。 如果一个结构具有两个不同的自定义派生对象,而第二个则发生恐慌,则错误范围将指向第一个,而不是发生恐慌的对象。


复制脚本

在一个名为demo_plugin的箱子中

#![feature(rustc_macro, rustc_macro_lib)]

extern crate rustc_macro;

use rustc_macro::TokenStream;

#[rustc_macro_derive(Foo)]
pub fn derive_foo(input: TokenStream) -> TokenStream {
    input
}

#[rustc_macro_derive(Bar)]
pub fn derive_bar(input: TokenStream) -> TokenStream {
    panic!("lolnope");
}

在另一个箱子里

#![feature(rustc_macro)]

#[macro_use] extern crate demo_plugin;

#[derive(Foo, Bar)]
struct Baz {
    a: i32,
    b: i32,
}

即使Bar惊慌失措,错误也会突出显示Foo

感谢您的报告@sgrif! 我已经更新了此问题的描述,并希望在那里也跟踪与宏1.1有关的所有未解决的问题。

嗯,自定义派生与#[structural_eq]的交互是我之前从未想到的。 宁可打扰我!

在我看来,在一天结束时,有一种将文本“附加”到令牌流中的方法可能是更好的接口……它将保留跨度信息避免此问题,不是吗?

更通用的接口的一个优点是,它允许程序包在字段上具有属性,当宏运行时可以将其剥离。 这是我所知道的允许自定义派生宏更改原始项目的唯一实际用例。

我认为接口问题归结为我们是否希望自定义派生成为“另一个宏”,在这种情况下,与其他程序宏(我们要修改原始项)具有相同的接口似乎很重要。 或者,是否应该使用特殊的接口来解决问题,在这种情况下,更具限制性的(附加)接口才有意义。

我会注意到,语法扩展在修饰符和修饰符之间早就有所区分,而且我认为所涉及的每个人都非常讨厌这种区别。 因此,我有点不愿意让定制派生变得有点特殊(已讨论的另一种选择是某种非常特殊的定制派生,甚至可能是声明性格式)。

@nrc好了,它仍然可以是tokenstream-> tokenstream,在这里我们只是不公开从头开始的new方法,对吗?

我同意必须有可能在字段中具有自定义属性,以使自定义派生真正有意义。 但是我相信,使用“追加”样式可以用多种方法来完成-当然应该对此进行讨论。 我绝对更喜欢追加样式,因为我更喜欢一个世界,在这个世界中派生宏不会更改该项并且是可组合的,而且它还能解决#[structural_eq]问题。 这恰恰是IMO的自由度。

如果人们不喜欢这个目的地,我想问一下为什么,因为很明显以前有足够的理由做出这种区分,不是吗?

(我想我建议的只是暂时的破解,并不能真正解决长期的可组合性问题。)

我的各种库当前都有看起来像foo!(Bar, parameters...)宏,这些宏从参数生成结构Bar

在IRC上的一些讨论中,我们想到了写#[derive(Foo)] #[params] struct Bar;的想法,并用将生成结构体的#[derive(Foo)]代替foo!

这显然不是一个有力的论据,但我真的很喜欢这个主意,因为对于用户来说,正在构建一个结构更加清晰。

我想知道我们是否可以对#[structural_match]进行返工,而不是放在生成的展示次数上。

(并不能真正解决问题)

值得一提的是,Serde和Diesel都在字段上大量使用了自定义属性,因此,非常需要自定义派生来允许替换。

因此,实际上也许我不是直接考虑#[structural_match]问题。 毕竟,自定义派生可以做什么?

  • 如果自定义派生错误地插入了#[structural_match]属性,则它应该无法通过稳定性检查。 如果没有,那本身似乎就是一个错误! (但是,考虑到该代码的工作方式复杂而古怪,这不足为奇。)
  • 如果自定义派生将其删除,则不会造成任何危害。

很抱歉写了很多小意见和大声思考,但还有其他担忧。 尽管自定义衍生工具可能无法“伪造” #[structural_match]批注(因为它会在没有“魔术跨度”的情况下结束),但它可能最终会弄糟任何现有批注的跨度,除非不幸的是,派生以正确的顺序应用。 基本上是@ colin-kiegel一直在谈论的不可组合性的一个实例,但是没有任何尝试在运行中修改结构的尝试。

(换句话说,由于我们依靠跨度来判断是否可以使用稳定的东西,因此丢失跨度信息可能会导致一些棘手的问题。)

编辑:好的,回读我看到我只是重新体验了@sgrif已经报告的内容。 再次抱歉。 ;)

这也有点麻烦,因为这意味着我们会将不稳定的实现细节公开给稳定的代码。 理想情况下,稳定的代码永远不会知道#[structural_match]批注存在。

@nikomatsaki很好,一种或另一种方式,我们需要强制执行不同的约束,具体取决于宏是作为自定义派生类还是其他某种类型。 这意味着无论函数的签名如何,都需要进行单独的处理(以及不同的语义)。

@ colin-kiegel

我绝对更喜欢追加样式,因为我更喜欢一个世界,在这个世界中派生宏不会更改项目并且是可组合的,而且它解决了#[structural_eq]问题。 这恰恰是IMO的自由度。

我认为变异宏是可以组合的,尽管组合性的术语当然不同。 显然,在派生操作上必须有一组前提条件和后置条件,这些条件可以由编译器强制执行,也可以由约定强制执行。 我们可以在这里选择不变量,这似乎是不变量的一种极端,并且请注意,我们已经在讨论可以软化这些变量的方式(例如,标记使用的属性)。 我认为通常来说,我们更喜欢最简单的条件,并由编译器强制执行。 但是,这应归因于应如何特殊处理自定义派生的问题。

如果人们不喜欢这个目的地,我想问一下为什么,因为很明显以前有足够的理由做出这种区分,不是吗?

我不认为当时有很强的动力。 我认为这很容易实现,并且“看起来像个好主意”。 之所以不喜欢它,是因为它使实现变得更加复杂,它为宏作者增加了通常无关紧要的区别,并且由于必须选择修改还是修饰而使宏的灵活性降低了。

我非常想考虑定制派生的长期设计,并确保我们朝着正确的方向前进。 在我看来,1.1解决方案的局限性和尽可能早地完成工作的愿望正在使这里变得一片混乱,而我们却没有看到更大的视野。

同意@jimmycuadra ,因为似乎很难以一种或另一种方式支持自定义属性。 @nikomatsakis也是正确的,尽管目前对#[derive(PartialEq, Eq)]处理还很差,我们不应该稳定它。 最后, @ mystor有一个很好的观点,即自定义派生模式甚至都不应该知道这个神奇的属性。 我们注定将来会添加更多,并且我不希望宏1.1阻止我们这样做。

同时也呼应@nrc关于自定义派生的长期设计的观点,我认为其中很多可以归结为#[derive]实际工作方式。 如果并且当我们支持任意属性时,我认为@nrc对于仅具有修饰符而不具有装饰#[derive]非常特别,其中自定义派生没有定义新属性,而只是附加到现有的。

现在,该实现对#[derive]模式进行了严格的从左到右扩展。 所有内部派生模式都会循环扩展,每当遇到自定义派生模式时,我们都会重新序列化,扩展,然后返回到阶段1。这又意味着编译器可能会多次运行#[derive]属性_multiple一次。输入definition_。 这导致一堆毛茸茸。

我可能有一个建议是调整#[derive]的扩展顺序:

  • 首先,宏1.1的所有自定义派生属性都被一个一个地扩展。 也就是说,如果您有#[derive(Clone, Foo)]我们将首先导出Foo ,其中结构具有#[derive(Clone)]批注。 如果#[derive]仍然存在,则可以导出自定义内置特征Clone
  • 其次,编译器未知的所有派生属性都将扩展为#[derive_Bar]属性。 这只是一个向后兼容的技巧,我们应该在某个时候删除它,这是用来扩展属性的方式。
  • 最后,编译器扩展所有已知的和内置的#[derive]属性

这具有令人惊讶的效果,您没有从左到右扩展,但是再次记住,这只是#[derive] 。 这为编译器提供了有关结构定义的最大知识,并且当它扩展内置特性时,我们知道类型的结构将永远不会改变。

听上去怎么样? 我相信它可以解决这里的所有限制?


@nikomatsakis我不确定将属性放置在impl是否可行,因为其他自定义派生模式在理论上可能会更改结构的布局,甚至更改字段的类型。 我认为这在首次扩展时违反了编译器的假设。

是否已通过Rust参考或其他方法正式声明了处理派生的顺序为从左到右? 一般来说,属性的顺序是否重要? 听起来像是通过这种方式实施只是偶然,宏编写者不应该依赖于从左到右的顺序。 Alex提出的处理自定义的建议首先产生,这样他们就再也看不到编译器添加的神奇属性了。

我只想补充一点,我不喜欢自定义派生可以更改结构布局的想法。 我希望能够将其用于对安全敏感的事情。 例如,考虑rust-gc使用的#[derive(Trace)]实现。

#[derive(Trace)]
struct Foo {
    a: Gc<i32>,
}

扩展到:

struct Foo {
    a: Gc<i32>,
}

unsafe impl Trace { // NOTE: Strawman impl
    unsafe fn trace(&self) { Trace::trace(&self.a) }
}

但是,如果允许更改结构中的字段,则可以定义Evil自定义派生:

#[derive(Evil)]
struct Foo {
    a: Gc<i32>,
}

扩展到:

struct Foo {
    a: Gc<i32>,
    b: Gc<i32>,
}

如果我们将它们结合在一起:

#[derive(Trace, Evil)]
struct Foo {
    a: Gc<i32>,
}

扩展到:

struct Foo {
    a: Gc<i32>,
    b: Gc<i32>,
}

unsafe impl Trace {
    unsafe fn trace(&self) { Trace::trace(&self.a) }
}

这是Trace 。 与rust-gc ,这会使b成为悬挂的参考,这是绝对不安全和不健全的。 这意味着对于某个类型的#[derive]Trace不再是安全的事情,这是非常不幸的。

我个人认为,任何表现良好的#[derive]都不会修改结构的布局/组成,如果这样做,那么您就不走运了。 自定义派生删除属性的能力至关重要,而放弃该属性并非一帆风顺。 另外,涉及某些形式的白名单或其他形式的其他实现与我们今天拥有的简单界面也有很大差异。

换句话说,我不认为让#[derive]永远不要修改结构的“纯度”值得花费。

我只是想知道是否有一种方法可以删除它的属性而不允许它添加或删除字段(例如,验证重新解析的结构中的字段与原始结构是否相同,如果不是,则会出错) t,但不要抱怨其他事物是否发生了变化)。

我对允许派生更改结构感到不好。 @mystor的示例就是我在高层次上谈论可组合性时想到的(也许这不是正确的术语)。

我想人们会利用它(如果有)。 这将迫使消费者对定制派生实现的细节及其执行顺序进行推理。

如果我能说“嘿,我不知道此派生有什么作用,但我理解另一个人”,而又不会相互依赖,我会更愿意。 否则,脚趾会感到疼痛,我相信这会发生。

程序宏是否确实在进行恶意操作,与您使用的任何板条箱在进行恶意操作有什么不同吗? 任何板条箱中都可以包含不安全的代码,该代码不应该执行某些操作。 似乎这种情况仅取决于您如何确定自己未编写的任何代码的可信度,例如社区信誉,自己检查源代码等。

我不认为板条箱会尝试做一些恶意的事情,而是希望它们变得“聪明”,并做一些巧妙的技巧来提高其实现效率,或者因为它们可以破坏其他自定义派生实现。 如果某些自定义派生方法开始将字段添加到仅在其实现中使用的结构中,因为它们可以,然后破坏诸如Trace之类的东西,我就不会感到惊讶。

@mystor在理论上听起来很相关,但是请记住,您实际上需要在Rust中提供结构的所有字段,因此像C ++那样,它像这样默默工作的可能性要小得多。

@alexcrichton

我可能有一个建议是调整#[derive]的扩展顺序

我喜欢这个主意。 就文档而言,可能需要说明的只是扩展顺序是“未定义的”。 最近几天,我们碰巧扩展了PartialEq / Eq,但没有严格的理由。

至于修改结构定义的派生,我同意这听起来有些微妙,但这并不过分。 我还认为“自动插入”字段可能非常方便-但我更希望此类修饰符具有独特的属性,而不是放在派生列表中,主要是因为我们不希望人们依赖顺序刚刚扩展。

PS。 我会认真考虑使用由板条哈希或类似内容作为种子的(确定性)RNG对用户自定义派生的扩展进行重新排序,以使人们不能依赖于扩展顺序。 我一直想在某些情况下执行此操作以防止隐式依赖,但从未获得过机会。 ;)

编辑:我改变了主意,再也看不到任何理由不允许对该结构进行变异,但这是我对上下文的原始评论

因此,据我所知,以下是允许自定义#[derive]改变结构的参数:

  • 在某些情况下可能有用(还没有看到任何示例,但我相信它们存在)
  • 我们希望能够在使用属性后将其删除
  • 赋予自定义派生作者更多的权力

为自定义#[derive]实现添加限制的参数(例如要求结构名称和结构中的字段/字段名称保持相同)是:

  • 它允许由自定义#[derive]生成的代码取决于为其健全性而派生的类型的结构(例如,来自rust-gc的#[derive(Trace)] ,_必须_查看实际的支持类型,或不健全,可能以一种微妙的“用后释放”的方式使用)
  • 它减少了宏扩展中隐式依赖的可能性,因为通过结构在它们之间传递的信息较少

我认为,编写健全的自定义派生实现(生成依赖于不安全代码的unsafe trait impls或代码)的能力非常重要,我相信在第一部分中可以通过多种方法实现大多数功能( (以安全的方式添加结构字段的功能除外)。 如果我们没有某种形式的约束,我认为像rust-gc这样的板条箱将不可能以安全的方式实现。 我有两个想法:

想法1

在运行自定义派生遍历之前,请读取结构的名称以及每个字段的名称。 传递完成后,并重新解析该结构,请检查该结构的名称是否相同,以及每个字段的名称(和字段数)是否相同。 如果不是,请提出错误并终止构建。

这将确保我们期望定制衍生插件依赖的基本结构特性不会被破坏,并且意味着我们拥有更多,完善的定制衍生插件。 这还具有与当前方法向后兼容的优点,因此,如果我们决定将来更喜欢它,我们可以切换并破坏任何人的代码。 就像今天一样,它也处理未使用的属性情况。

想法2

为每个自定义派生插件提供相同的输入TokenStream(程序中编写的原始文本)。 重新分析结果后,记录输出结构上仍然存在哪些属性。 如果每个输出令牌流中都有一个属性,则抱怨未使用的属性。

这意味着不可能具有排序依赖关系(从概念上讲,每个自定义派生插件都可以在同一个原始对象上运行),并且还不可能破坏插件的结构。 我喜欢这个想法,因为它可以确保每个自定义派生方法都以一种理智的方式运行,仅根据现有结构生成新项。 这也很容易转换成我们可以将当前解决方案转换成的任何解决方案。

TL; DR

总而言之,我想了解允许变异结构的特殊优势,以及为什么它超过了使安全#[derive(Trace)]和始终正确的#[derive(Serialize)]等成为可能的安全问题。 我敢肯定,如果我们最终涨难跌,突变结构的路线,将有一个很好的理由,但我会很伤心改变我跟踪的名称自定义导出到#[derive(unsafe_Trace)]

我发现@alexcrichton的解决方案是一个不错的权衡。 我绝对希望某些自定义派生类所做的任何更改,都将应用默认更改。

即使@mystor有一个好点,那就是它可能导致令人不愉快的意外,但更改struct的可能性似乎是强制性的。 另一方面,将程序宏的用例与不安全的代码和安全问题结合在一起的包装箱似乎很少见。

离题:此实现是否将为宏提供一种优雅地报告错误的方法?

我喜欢@nikomatsakis的想法来随机化自定义派生的扩展顺序。 这无疑将有助于防止“不良”习惯变得太流行-不应做太多的努力。

@mystor第三种选择是在应用所有派生之后仅进行一次安全检查。 那不是100%的声音(两个派生可以添加和删除相同的字段),但是就启发式对策而言,应该足以防止任何企图更改自定义派生中的结构定义的尝试。

我真的看不到有关结构修改的问题。 不能无形地添加字段,初始化期间必须注意一些事项。 如果您真的担心这种情况发生,可以编写您的派生代码以生成代码,如果该代码无法轻易看到整个结构,则该代码将无法编译。

@sgrif在_most_情况下可能是正确的-但如果您还派生并使用默认特征或等效特征,则不是很多。

@sgrif PS:的确,大多数生锈的作者可能了解他们自己的代码中发生的事情,因此,如果他们有意选择使用此类宏,则可能不会因结构更改而感到惊讶。

当然,在结构上应用宏的一般用例当然是struct-alterations +装饰的_combination_。 但是我希望总体比率是“只有少量改动”的“大量装饰”。 最好在此处进行清晰的分隔,因为这样可以提高可读性和灵活性。

  • 灵活性:假设您要应用两个自定义派生,这两个都可以做,即更改和修饰。 无论您如何订购它们,都可能无法获得所需的结果。 但是,如果更改是通过不同的机制发生的,并且装饰都在以后应用,则您可以灵活地以可控的方式组合多个更改和装饰。
  • 可读性:如果您阅读别人的代码,并且有10个派生应用于一个结构,而其中一个更改了结构,则您将需要更多的时间来解决这一问题。

因此,虽然我确实发现@mystor的论点的这一部分令人信服:

它允许由自定义#[derive]生成的代码取决于为确保其完整性而派生的类型的结构(例如,来自rust-gc的#[derive(Trace)],它必须查看实际的支持类型,或者它是不完善的,可能以一种微妙的“用后释放”的方式使用)

我认为试图在派生中强制执行此操作可能是错误的处理方式。 具体来说,我们可能确实希望能够在一般情况下修改结构定义(是的,它不是透明的,但是会透明)。 这意味着可能需要以某种其他方式解决具有“声音” Trace的想法,该想法可以确保结构之后不会更改。 考虑是否从下到上应用装饰器:

#[mangle] // <-- custom decorator that does bad things to struct definition
#[derive(Trace)]
struct Foo {
    x: T, y: U
}

一个想法可能是Trace impl可以这样写:如果struct定义更改,则_it_无法编译。 例如:

unsafe impl Trace for Foo {
    fn trace(&self) {
        let &Foo { ref x, ref y } = self;
        <T as Trace>::trace(x);
        <U as Trace>::trace(y);
    }
}

请注意,尽管#[mangle]确实可以使您恶作剧,但也可能使您的印象不妙。 =)我们在这里只能做很多事情。

作为这些对话的观察者,我很高兴拥有正式或非正式的规则,即只允许#[derive]添加impl块并引入同级注释( #[mangle(Foo, Bar)]听起来me)对类型结构的更改专用。 #[mangle]可以具有定义的评估顺序。

如果没有注释,则注释之间可能需要有一个已定义的评估顺序。

我认为我对此的看法已经放松。 @nikomatsakis提出了一个很好的观点,即使我们摆脱了在[[derive]中对结构进行变异的操作,我们仍然无法在代码中对结构布局进行假设。 let Foo{ ... }技巧似乎可以确保在合理的情况下布局正确。 但是,我们可能需要将其记录在某个地方,以便每个人都不必独立发现它。

@nikomatsakis

  • mh ..可能有其他规则,必须在_subscribe_之前应用_custom装饰器,再加上_derives不能更改item_。 通常,这仍将允许强化/纯化派生机制。

但是我也很高兴看到还有另一种方法可以强化_individually_自定义派生以抵御以后的更改。 :-)

关于需要修改应用于属性的项目内部的情况,我在u.r-l.o上碰到了“带有Rust的Qt的预实现反馈”线程,并发表了这篇文章:

https://users.rust-lang.org/t/pre-implementation-feedback-for-qt-with-rust/7300/19

一个值得注意的方面是,在这里,我建议将#[derive] (或类似的东西)应用于_trait_而不是结构-并且添加在其中的项目将是const特质方法。

与我上面提出的货运问题类似,我不确定

#[macro_use]
extern crate double;

从名为double的板条箱中导入过程宏。 由于我们在编译时使用的是运行时代码(就像在真实的Ruest中一样),因此应该有一个类似于Racket的(require (for-syntax ...))的阶段递增import语句。 [球拍文档是https://docs.racket-lang.org/reference/require.html#%28form ._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._for-meta%29%29,不幸的是,我不知道如何链接正确的部分。]

博客文章http://blog.ezyang.com/2016/07/what-template-haskell-gets-wrong-and-racket-gets-right/指出了模板Haskell中的定相错误,可能与- -我不想在Rust中犯同样的错误。

@ Ericson2314 Rust的不同之处在于double已经针对特定阶段进行了编译。
也就是说,板条箱仅导出宏/修改器接口,就好像它们是用macro_rules
能够创建既可以导出程序宏又可以导出底层Rust项目(构成程序宏)的板条箱很有趣,但是到目前为止,似乎还没有提出任何建议。

允许正在创建的板条箱选择很多有关导出内容和方式的方法,但是从具有不同编译模型的LISP批发系统似乎适得其反。

是的@eddyb我对这种“创造知道如何将其用于下游”的方法表示怀疑。 由于我们的编译模型,如果有什么阶段对我们来说比Racket更重要(我什至不确定Racket是否可以交叉编译),那么我就没有最后一个争论了。

被提名参加lang团队讨论:稳定计划。

在serde方面,这是剩余问题的简短清单,在我们停止支持现有的编译器插件并正式向所有夜间用户推荐Macros 1.1之前: https我们唯一需要Rust来解决的是#36211。 我们正在迅速进行的其他所有工作。

我有一个不使用syntex即可实现rustc_macro的PR open,因此我们可以不再担心编译时间https://github.com/serde-rs/serde/pull/548。

编辑:没关系,#36211只影响了旧的syntex实现

我将尽力在星期五之前完成Diesel港口的工作,以便我可以确认这样做是否可以满足我们的要求。

@dtolnay提供了serde-rs / serde#548,是否还有其他针对serde的阻止程序?

@sgrif很棒,谢谢! 完成此操作后(或之前),您可以在这里对遇到的任何其他阻滞剂发表评论吗?

好,我会的。

2016年9月27日星期二,晚上7:29 Alex Crichton [email protected]
写道:

@dtolnay https://github.com/dtolnay提供了serde-rs / serde#548
https://github.com/serde-rs/serde/pull/548 ,是否还有剩余
Serde的阻挡者?

@sgrif https://github.com/sgrif很棒,谢谢! 完成之后
(或之前),您是否可以在此评论您剩下的所有阻滞剂
遇到了?

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub上查看
https://github.com/rust-lang/rust/issues/35900#issuecomment -250028743,
或使线程静音
https://github.com/notifications/unsubscribe-auth/ABdWK7MnA1rpn-WGji8gwAT2JCIqB4LFks5quaa-gaJpZM4JqEAX

@alexcrichton

给定serde-rs / serde#548,还有其他针对serde的阻止程序吗?

不,如果@ oli-obk或@erickt今天或明天查看该公关,那么我可以在第二天将所有内容推送出去,并迁移Rusoto等一些知名用户。

@dtolnay我在Ruma中大量使用serde_macros,如果您需要更多的眼睛,也希望帮助您测试serde_derive。

实际上,我也使用Diesel_codegen,因此Ruma是这两个库的Macros 1.1版本的良好测试平台。

我发布了Serde 0.8.10,宣布不推荐使用serde_macros,而赞成Macros 1.1。

让我们为“货物文档工作”添加一个复选框-https: //github.com/rust-lang/cargo/issues/3132。

@dtolnay完成了!

不知道这些是serde_derive错误还是rustc / rustdoc错误,但是我注意到生成的文档中存在两个问题:

  • 文字“ ///”出现在使用自定义派生类型的生成文档字符串的开头。
  • 对于使用自定义派生的类型, DeserializeSerialize不会显示在“特征实现”部分中。

文字“ ///”出现在使用自定义派生类型的生成文档字符串的开头。

这是一个syn错误。 我发布了0.8.2的修复程序,因此cargo update对其进行了修复。

我不知道第二个,将其留给@alexcrichton。

@jimmycuadra

  • 对于使用自定义派生的类型, DeserializeSerialize不会显示在“特征实现”部分中。

听起来很腥! 看起来这虽然是rustdoc中的错误,但可能不是新错误。 例如,rustdoc不会在Copy实现中显示此内容:

#[derive(Clone)]           
pub struct Point {         
    x: i32,                
    y: i32,                
}                          

const _FOO: () = {         
    impl Copy for Point {} 
    ()                     
};                         

serde-derive使用该模式来生成impls。 @dtolnay serde_macros是否也具有相同的生成形式? 暂时离开以解决文档问题是否容易?

@jimmycuadra想要打开一个单独的bug来跟踪rustdoc问题吗?

@dtolnay serde_macros是否也具有相同的生成形式?

是。

暂时离开以解决文档问题是否容易?

不。据我所知,这是同时满足这两个约束的唯一解决方案:

  • 必须在板条箱根目录中不支持serde,即不支持::serde 。 当人们将serde impls放在单独模块中的功能标志后面时,这很常见。
  • 必须能够使用周围模块中的私有类型。

有关详细信息,请参见https://github.com/serde-rs/serde/issues/159

@dtolnay嗯,所以要澄清一下

更新:尚未完全完成端口,但已完成。 除了我已经报告过的事情或我们知道会遇到的次要限制外,没有其他问题。 可能很安全的选中“柴油机”复选框,如果今晚没有发生,我们明天将在Macros 1.1上发布该版本。

对于随后的那些人,我几乎在所有地方都打开了#36945将rustc_macro重命名proc_macro ,这似乎是该条木箱名称的共识。

看起来rustdoc中缺少的特征实现(现在正在另一个问题中进行跟踪)并非特定于Macros 1.1。 安全无视。

我试图将mockers库从编译器插件移植到宏1.1,但出现“错误:自定义派生属性只能应用于结构体/枚举项”。 使用编译器插件可以(虽然有些奇怪)对特征使用“派生”。 我在这里不走运吗?

@kriomant有趣! 我认为这实际上可能是当今自定义派生中的错误,因为我不确定它是否曾经打算允许#[derive]应用于特征...

我认为,现在保守起见,我们可能暂时还不能“稳定”特征,但是我们可能会为其添加一个不稳定的功能。 有想法@ rust-lang / lang吗?

@alexcrichton从custom_derive方面来看,特征和结构之间有什么区别?

@KalitaAlexey没有,匹配真实的derive实现人为的限制

@alexcrichton我们可以扩展对特质的支持吗?

正如我上面提到的,自定义派生可能会允许对特征进行自定义派生,而RFC中未对此进行说明,因此在扩展之前需要进行更多讨论。 无论如何,在第一遍中不太可能稳定对特征导出的支持,但是我们可以考虑使用单独的特征门。

我个人不希望添加特征上的派生类,因为这似乎更像是自定义属性领域,而不是派生本身。 (例如,违反了最初创建的#[derive]的精神)

我希望自定义派生遵循与常规派生完全相同的规则。 我们可能会在以后进行更改,但是我认为应该进行RFC修改(我对这个想法也很冷漠,但是我可以通过一个引人注目的用例和对各种边缘情况的合理处理来改变主意)。

@alexcrichton

我认为,现在保守起见,我们可能尚无法稳定特征继承,但是我们可能为此添加一个不稳定的功能。 有想法@ rust-lang / lang吗?

👍来自我。

好的,不稳定的功能很好,但是这意味着我的库无法在稳定的状态下工作(没有代码生成)。 而且“ macro!(…) 1.1”也没有涵盖

我正在重新访问proc宏RFC,并且计划是将宏板条箱声明为#[cfg(macro)] 。 对于宏1.1,我们不需要这样做,但确实需要特殊的板条箱类型。 这两种机制有点正交-第一种描述编译的阶段,第二种描述板条箱的类型。 但是它们也有些重叠-后者暗示前者。 前者还可以扩展为在同一包装箱中声明proc宏和非宏功能,而后者则不行。

我不确定是否需要在此处进行任何更改,我们可能会将proc宏建议修改为向后兼容(它故意省略了描述加载宏的机制以及由此创建的板条箱类型)。 但是在稳定时期需要考虑的事情。

@kriomant正确,是的

@nrc现在实际上已定义为cfg(rustc_macro) (尽管很快将更改为proc_macro )。 我们不需要它,不是,但是我认为对于链接到板条箱的编译时以及运行时这两个概念都是必要的? 也就是说,我们将编译proc-macro板条箱两次:一次使用板条箱类型proc-macro ,一次使用板条箱类型rlib ,而后者将不会链接到libsyntax或类似的东西那。

现在,虽然不_require_似乎很好,但是我想那意味着以后您必须选择运行时支持? (将板条箱编译两次)

@alexcrichton可以暗示#[proc_macro_derive]吗? #[test]表示#[cfg(test)]
(这并不意味着我们现在必须添加它,只是如果我们添加cfg的最坏情况是未使用的项目警告。)

@eddyb extern crate proc_macro;怎么样? 我觉得那些东西也会破裂。

@mystor这只是一个带有许多类型和impls的常规箱子。

它不是也链接到libsyntax吗,这意味着,如果一个板条箱使用proc_macro板条箱并想要交叉编译,它还必须交叉编译语法吗?

@eddyb#[proc_macro_derive]可以被自动忽略,但是问题是我们也必须忽略那些函数传递的所有内容(例如extern crate proc_macro )。 尽管它只是“板条箱”,但它对运行时有严重影响(动态链接,不可用于交叉编译的目标等)。

请注意,测试具有#[cfg(test)] ,因此对我来说,出于基本相同的目的,我们仍然给出#[cfg(proc_macro)]似乎很合理。

@alexcrichton _Ideally_,该板条箱只能输出什么,不需要动态链接或类似的东西,只有libstd 。 但是,我可以看到它在#![no_std]情况下引起了问题。

听起来,如果我们想捕获所有:dis named :,就必须从头开始进行两次编译

编辑:等等,我什至在想什么? 它需要自定义的板条箱类型,双重编译适用于_regular_板条箱,这些板条也可以__导出_程序宏/属性/派生对象/等。 因此暂时不相关。
但是我们至少可以引入始终为新的板条箱类型设置的#[cfg(proc_macro)]

feature此功能正进入其最终评论期,旨在在此发行周期结束时保持稳定! 🔔

现在考虑稳定的理由是:

  • 根据设计,此功能的API表面是“保守”的。 同时,它与程序宏的事实计划是向前兼容的
  • 该功能正在Serde和Diesel中迅速得到广泛使用,尽管值得注意的是,广泛使用主要是作为自定义派生的“客户”。
  • 现在进入FCP可以使我们在该功能真正稳定交付之前三个月,这应该是足够的时间来解决所有剩余的问题。

请注意,基于对该线程的早期共识,名称已转移到proc_macro 。 在本发布周期的其余时间里,我们可以继续对此以及其他所有要点进行分析。

两次编译包装箱听起来很粗糙:作用域设置无法正常工作。 真的像extern! crate needed_for_my_inline_proc_macros;解决方案好得多。

@aturon什么,这天前人们不是刚刚开始使用吗?

@ Ericson2314比这更长的时间,但是正如FCP评论所解释的,从现在开始到交付稳定频道的最短时间是三个月,对于这种极其狭窄的界面,我们觉得这绰绰有余。

请注意,FCP本身为期6周,这并不一定意味着我们甚至会将其推向稳定之路。 但是,至少从现在开始,假设在此之前没有发现问题,我们将有机会在3个月内发布此功能。

喔好吧。 我记得3个月的时间,但忘记了FCP在这3个口中的位置-至少从8月22日起3个小时。

我确实认为我提出的分阶段问题确实会影响proc宏故事的一小部分,因此我希望看到这些已解决的问题。

@alexcrichtonrustc_copy_clone_marker和其他rustc_attrs仍然在咬我们。 参见https://github.com/serde-rs/serde/issues/577。

#[derive(Copy, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct MyStruct {
    value: i64,
}

解决方法是交换订单,但我想我会检查这是否可修复。

#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone)]
pub struct MyStruct {
    value: i64,
}

我已经完成了将https://github.com/sfackler/rust-postgres-derive移植到宏1.1的工作,虽然比较容易,但是处理多个派生类读取的属性却非常麻烦。 Serde遇到了同样的事情,但是需要一堆怪异的逻辑来同时扩展两个派生类: https :

它不应该妨碍稳定,但是我认为这是作者身份方面相对较大的人机工程学问题,我们应该尽快解决。

我发布了quote 0.3.0并支持macro_rules!风格的重复(感谢@nrc的帮助)。 如果您正在开发不具有准/语法的proc宏,则可能需要这样做。 来自postgres的示例用法:

pub fn enum_body(name: &str, variants: &[Variant]) -> Tokens {
    let num_variants = variants.len();
    let variant_names = variants.iter().map(|v| &v.name);

    quote! {
        if type_.name() != #name {
            return false;
        }

        match *type_.kind() {
            ::postgres::types::Kind::Enum(ref variants) => {
                if variants.len() != #num_variants {
                    return false;
                }

                variants.iter().all(|v| {
                    match &**v {
                        #(                           // \
                            #variant_names => true,  //  |----- new feature
                        )*                           // /
                        _ => false,
                    }
                })
            }
            _ => false,
        }
    }
}

我很好奇,为什么#而不是$呢?

编辑:一直认为引用语法将是${...}但即使已经过去了几年,也许我还是太喜欢ES6模板文字。 \{...}也可以使用,尽管它在其他地方很有用。
没有括号似乎很难发现,但我不用担心。

我很好奇,为什么#而不是$呢?

因为它是一个macro_rules宏,我无法做任何我想做的事情:微笑:。 $v作为单个令牌被匹配,并且没有办法从$v转到使用变量v 。 相反, #v是两个令牌,因此我可以分别将它们匹配并使用ident做事。

macro_rules! demo {
    ($tt:tt) => {};
}

fn main() {
    demo!($v);
}

等待所有这些都在macro_rules ?中完成! 暂时忘记了宏1.1。 也就是说, $x是一个令牌是IMO的设计缺陷。 cc @jseyfried

@dtolnay

@alexcrichton rustc_copy_clone_marker和其他rustc_attrs仍在咬我们。 请参阅serde-rs / serde#577。

哦,亲爱的,这似乎很糟糕! 我将在顶部更新列表。 @nrc@jseyfried关于我们如何解决此#[derive]属性被扩展时,它可能会试图吞并所有未来的属性,然后首先这样做吗?

@sfackler

多重派生读取的属性的处理是一个巨大的痛苦

我不确定我是否完全遵循您链接的示例,能否详细说明?

@alexcrichton为例

#[derive(ToSql, FromSql)]
enum Foo {
    #[postgres(name = "bar")]
    Bar
}

#[postgres]属性用于调整ToSqlFromSql实现的生成方式。 它需要在最终输出之前删除,因为否则它是一个未知属性,但是ToSqlFromSql实现是分别运行的。 如果您仅通过生成实现然后剥离属性就天真的方式,则第二个派生将缺少定制。

现在,通过将两个派生的impls的实现转发到一次生成两者的相同函数,即可解决此问题的serde-derive和postgres-derive黑客。 自编译器剥离它以来,我们必须重新附加当前正在调用的#[derive] ,然后将其发送以进行扩展: https :

@sfackler我_think_您也可以通过仅在没有剩下来自同一实现者的派生类时才去除额外的属性来做到这一点。 _shrug_可能更好。

@sfackler啊,很好,很

我不知道这种逻辑是否会很脆弱。 例如,如果您要扩展ToSql如何检测到FromSql的将来扩展? 它可能在上面提到的@dtolnay这样的#[cfg]之后? 因此,可能无法在所有情况下都进行检测吗?

@alexcrichton是的,它很脆弱,这就是为什么真正的解决方案似乎很重要的原因。

#[cfg]#[cfg_attr]会在衍生之前进行处理吗?

由于宏1.1 API适用于字符串,因此我只能想象三种解决方案:

  1. 保持原样并等待宏2.0
  2. 允许自定义派生中未使用的属性
  3. 通过允许自定义派生将属性名称推送到当前项目的特定白名单来扩展宏1.1 API

每个选项都有自己的优点/缺点。
缺点1:与此同时的变通方法
缺点2:一些未使用的属性将不会被检测到
缺点3:临时解决方案使用更多的api表面

我考虑过将属性包装在#[used(...)]以同时保留它们和将它们列入白名单,但这实在太傻了,而且太不稳定了。

@alexcrichton

当一个#[derive]属性被扩展时,它可能会试图吞噬所有未来的属性

我喜欢这个主意。 由于#[cfg] s和#[cfg_attr] s在#[derive] s之前进行处理,因此#[cfg]受保护的#[derive] s对于这种方法来说不是问题(或用于@sfackler的属性剥离问题的类似解决方案)。

这种方法会使@sfackler的解决方案更加简单,因为其他相关派生只能位于最后一个属性中(我也认为@eddyb建议可以简化事情)。

控件属性问题的一种可能性是添加扩展后回调,与syntex的回调有点类似: https :

您的派生impls可以是独立的,而不是剥离控制属性,并且您可以注册在完成所有扩展以清除之后运行的过程。

@dtolnay由于@jseyfried,您之前对多个#[derive]属性的关注应该很快得到解决

我使用synquote软件包来玩这个游戏,并获得了非常积极的体验。 稳定后将是一个很大的功能。

属性问题目前正在困扰我,因为它是serde派生逻辑的下游。

忽略派生代码中未使用的属性,或允许在常规派生之后运行的后期扩展以剥离它们似乎对我来说是合理的解决方案。

如果宏没有选择权来更改原始令牌流,那将是理想的选择,这似乎是一个危险的功能,最终可能会在现在和宏2.0之间被广泛使用,因此以后很难删除。

因此,我在泛型函数内部的类型路径中缺少分号,并从rustc得到以下错误消息。 我想知道是否可以提供更多信息,例如哪个衍生宏导致lex错误,哪个令牌导致它等等。

error: custom derive attribute panicked
  --> src/simple.rs:69:1
   |
69 | struct C(u64);
   | ^^^^^^^^^^^^^^
   |
   = help: message: Failure parsing derived impl: LexError { _inner: () }

我为LexError做了一个测试用例,它似乎发生在10月13日每晚(rustup没有更新) https://github.com/keeperofdakeys/proc_macro_derive_test。

@ rust-lang / lang我认为我们需要讨论派生对象是否可以修改其带注释项的问题。 尽管这是允许它的简单解决方案,但它似乎违反了用户的期望,并且似乎并不受欢迎。 我仍然更喜欢当前的解决方案-简单性很好,并且没有任何提议的替代方案令我印象深刻。

如果可以修改项目,我们将需要一些解决方案来控制属性。 只要(在将来)非派生属性宏可以,我对保持派生修改项的能力就不会感到特别强烈。

有人应该定义“修改”。 我们是在谈论修改结构的成员,还是在谈论剥离属性,以防止编译器抱怨未知属性?

如果我们谈论的是修改结构的成员,我认为最好的解决方案可能是在每个结构上只允许一个结构修改宏,然后再扩展所有其他结构。 这将具有明确的(在我看来)预期的行为。

如果我们只是在谈论剥离属性,那么应该有一种将属性集成到宏机制本身中的方法,而不是由宏自行决定。

我对派生行为的直觉是,它增加了impls。 对于最终用户,派生似乎应该添加impls而不执行其他任何操作。 特别是,它们不应以对其他派生类很重要的方式修改结构,因此,它们应与顺序无关。 我们是否同意这是派生类应该表现的方式,还是人们认为派生类也应该能够对附加类型执行转换?

如果我们对此表示同意,那么问题就变成了-我们是希望我们公开的接口来强制执行此操作,还是我们想让其派生出作者来强制执行此操作? 在这里,似乎存在两个抵消问题:

  • 对我来说,强制执行派生的行为契约似乎更像Rust解决方案。
  • 使serdedeisel可以使用该约束带来了很多挑战。

当然,其他属性如何修改结构似乎与派生方式(特别是行为方式)无关。

值得一提的是,删除带注释的项目的功能使当前系统可以担当许多其他角色所不能提供的角色。

@sgrif您能否详细说明deisel如何使用derive? 我感觉好像我了解serde如何使用其修改结构的能力(删除通知宏以忽略或重命名序列化特征中的字段的属性),但是deisel可能正在使用它来做其他事情。 当您说“删除带注释的项目”时,似乎您正在对该项目执行非常彻底的转换。

@withoutboats 90%或多或少是您所期望的。 我们不触摸用户提供的项目。 不过,我们会删除带注释的项目,以将爆炸宏入侵到系统中。 https://github.com/diesel-rs/diesel/blob/master/diesel/src/macros/macros_from_codegen.rs#L12 -L18。 除此之外,我们唯一触摸输入令牌流上的任何内容都是剥离注释。 https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L109 -L120

@sgrif我也滥用了定制派生来获取程序爆炸宏。 我个人更希望在宏1.1中有一个程序化的bang宏系统,这样我们就不会因为过多而滥用过多该功能。 我觉得一个好的方法是也要获得几乎相同的程序爆炸宏故事(可能具有一些笨拙但简单的卫生条件,例如传递给宏的TokenStream中不存在的所有标识符都被卫生条件隐藏了吗? (确切地不知道会是什么样子),并使用它代替滥用自定义派生,然后使自定义派生无法修改其上的结构。 这样,我们就可以启用更多的包装箱,提高ux,并使派生理智。

但是,我可以理解将保持为一个简单的宏的论点。

我的观点是,宏1.1的目标是尽可能灵活地满足尽可能多的需求,并且维护负担很轻,因此它可以快速稳定下来并充当直到宏2.0为止的权宜之计。 我认为当前的设计非常适合该角色。

如果我们谈论的是要永久担当此角色的事情,我会更反对

也许我错了,但是我对RFC的阅读是,这旨在成为永久派生行为的基础。 就是说,将来会有更多方法添加到TokenStream中,但是派生宏将使用此API,该方法目前允许它们对带注释的项目执行任意更改(这是Deisel用例所必需的) )。

对于允许派生永久执行此操作,我感到非常消极。 如果我们接受这个系统,以及macro_rules宏,在将来的某个时候将不推荐使用,并且在宏2.0下,一个更受限制的不同派生API将是首选,我是比较舒服。

似乎打算将装饰器支持为可以做任何事情的变压器。
并将其公开为一个简单的装饰器,该属性中没有输入。

@withoutboats :serde支持许多属性来修改生成impls的方式,因此我们绝对需要能够在处理完这些

@eddyb我赞成装饰能够执行任何操作,但不赞成派生为不受约束的装饰器(从长远来看)。

@erickt对。 我认为,从长远来看,理想的解决方案是将这些属性注册为无操作自定义属性,而不是由派生程序负责剥离它们。 但这在短期内是不可行的。

我认为,从长远来看,理想的解决方案是将这些属性注册为无操作自定义属性,而不是由派生程序负责剥离它们。 但这在短期内是不可行的。

我不熟悉使编译器内部在短期内不可行的编译器,但自定义派生插件是否有可能提供打算剥离的自定义属性列表,然后拒绝对属性进行任何其他转换原始项目?

我还注意到,Diesel不遵循Serde的方法将所有自定义属性都用一个名称(例如#[serde(rename = "name")]而不是Diesel的#[table_name = "name"] 。如果仅使用一个自定义属性名称,它将简化实现吗?被注册而不是列表?

控件属性问题的一种可能性是添加扩展后回调,与syntex的回调有点类似: https :

您的派生impls可以是独立的,而不是剥离控制属性,并且您可以注册在完成所有扩展以清除之后运行的过程。

我在post-expansion实现了Macros 1.1的扩展后回调。 您的派生impls可以是独立的,而不是剥离控件属性,并且您可以注册在所有扩展完成剥离属性后运行的传递。

我们今天在lang小组会议上讨论了修改/订购问题。 人们希望能够满足用户的期望,并希望达到简单化(就设计而言,没有太多的hacks出现在顶部,并且也不会遭受难以破译/调试的错误)。 值得注意的是,即使对目标数据进行修改令人惊讶,但这也不是不安全的。 也有人认为,我们可能出于纯粹的目的而不是出于积极的动机而趋于纯洁。

最后,我们认为不修改源代码的派生方法可能更好,我们应该更改该模型。 这可能意味着延长FCP期限。 我们认为应该没有专门的机制来处理剥离属性。 而是,编译器处理属性的方式应允许宏使用的属性保留在程序中。 RFC 1755应该考虑到这一点。

这将延迟某些自定义派生用户的稳定。 但是,我们认为,派生的大多数用法(尤其是那些使用户无法使用稳定工具链的方法)都不会使用属性,因此,例如,Serde的大多数用户将能够很快转向稳定。 那些需要属性的对象将花费更长的时间_,但这不会影响普通的case_。

cc @alexcrichton@dtolnay@sgrif@erickt-有何想法?

Serde和Diesel使用的属性可能比您建议的更常用。 仅凭我自己的经验,我很确定我所有使用Serde的程序都使用属性。 使用Diesel时,我肯定使用属性,并且我认为这是必需的,以便告诉柴油_代码生成哪个数据库表映射到该结构。

也就是说,不让custom派生对结构进行变异对我来说似乎是正确的选择。 它只是简化了整个过程,避免了很多奇怪的情况。 正确完成它比快速完成它更为重要,因此,如果该功能必须保持不稳定一段时间,这似乎也不错。

值得注意的是,即使对目标数据进行修改令人惊讶,但这也不是不安全的。

这不是不安全的,除非您派生了不安全的特征。

在我看来,自定义派生的用例之一是以安全的方式实现标记为不安全的特征,例如,必须准确描述其实现结构的成员布局的特征。

我的asn1板条箱还需要除琐碎用法之外的所有属性,因此我实际上需要等到自定义属性登陆。

将自定义属性rfc分为两个是一个好主意吗?

  1. 一个rfc,通常为自定义属性提供符号和命名空间(允许稳定的no-op属性)。
  2. 一个有关如何定义以及运行自定义属性宏的语义的RFC。

自定义属性宏看起来很充实,需要很多东西。 因此,将rfc分为两部分可能会更快地为自定义派生包提供稳定的属性。

这也意味着属性是用名称隔开的,并且是有意的(即,需要外部包装箱才能将属性用于包装箱)。 我可以预见,防止使用相同属性名称的两个自定义派生宏是一件好事。 一个很好的期望是使用特征所在的包装箱名称作为属性。

为什么没有实现此实现不包含通常的name!宏的原因? 对于常见的宏,简单的TokenStream输入, TokenStream输出对于在害虫中非常有用,在这种

@dragostis引用

在编译器中提取当今程序宏系统的一小部分,足以使诸如自定义派生工作之类的基本功能具有最终稳定的API。 确保这些功能不会给编译器造成维护负担,也不要尝试同时为“完美的宏系统”提供足够的功能。 总体而言,这应被视为朝着正式的“宏2.0”迈进的一步。

或者更实际地说,要使程序宏系统运行良好就需要大量的设计和测试,就像我们现在构建的封闭系统一样。 serdediesel类的箱子存在严重的可用性问题,而没有适当的自定义派生功能,因此让我们采取一个权宜之计以立即对其进行修复-碰巧还可以帮助工具和经验程序宏系统。 synquote板条箱就是很好的例子。

@keeperofdakeys是的,知道了。 我不是在要求宏2.0的实现,我只是想知道添加另一个属性是否涉及任何负担,比如说proc_macro是一个最小的实现,它仅反映了仅针对常规宏的派生设计。 害虫中的示例仅会为struct派生一个解析器,而不会从struct本身派生而来,而是从{}之间定义的简单语法派生。 不过,我希望我不要再进行枯燥的讨论!

@dragostis我前段时间在内部提出了这种可能性。 您可以在此处阅读回复: https :

@nrc

也有人认为,我们可能出于纯粹的目的而不是出于积极的原因而趋向于纯洁。

关于这一点的一个注意事项:在很多情况下,我们必须做出艰难的决定,例如参数化和专业化,静态地确保“纯度”的相关概念将是困难/不可能的。 但是在这种情况下,通过构造来保证它实际上非常简单,并且消除派生顺序的重要性对于用户来说似乎是一个非常简单的简化。

就稳定性而言,我们可以考虑立即添加一种用于忽略属性的不稳定机制,该机制在实践中将是稳定的(因为API仍然需要每晚使用,因此它不容易损坏)。 我们甚至可以考虑稳定这种旁通道,并计划弃用该旁通道,以支持一种更通用的机制。 或者,我们可以考虑@keeperofdakeys的建议,并快速推动解决一般问题的通用属性解决方案。

从我的角度来看,重要的是所有这些关注点都不能显着阻止宏1.1广泛用于Serde并至少“事实上”稳定(即不中断)。

@sgrif

如果我们谈论的是要永久担当此角色的事情,我会更反对

我想回声@withoutboats并澄清当前意图是即使在发布宏2.0时,宏1.1 API表面也保持原样。 不过,我们可以重新考虑这一点-我们可以更像今天的macro_rules来考虑它,并打算在最终系统就绪后就弃用。

我对自定义派生的属性用法的印象@jimmycuadra印象相似,这是它们非常常用。 我相信(如果我错了,请纠正我)自定义属性对于许多柴油机用例至关重要。

从这个意义上说,我不确定100%关于这些属性的最佳方法是什么。 稳定宏1.1只是可惜的,但是仍然每晚有大量用户(即使它是“事实上稳定的”每晚),因为这在某种程度上违反了快速稳定宏1.1的目的。 换句话说,如果我们不实际将大多数宏1.1用户拉到稳定的Rust上,我认为可以等待稳定的宏1.1。

不过,困扰我的一点是,我们认为自定义属性从长远来看会是什么样,并使用当前的宏1.1系统对其进行合理化。 在当前的RFC中,自定义属性的包装箱必须在包装箱顶部声明白名单的名称空间。 这意味着在自定义派生中使用自定义属性与在其他地方使用自定义属性完全不同。 这种断开连接让我从人体工程学和“最少惊喜”的角度担心,尽管我也看到它是调整RFC的强制功能(例如,如果我链接到serde板条箱,也许可以将白名单属性自动列入白名单) )。

总的来说,我个人会很好,因为现在可以完全稳定Macros 1.1系统。 或者换句话说,稳定以下事实:自定义派生扩展函数接收一个结构,然后如果它们希望保留该结构,则也必须返回该结构。

我倾向于从rustc-serialize切换到serde,因为serde的代码生成支持控件属性。

我们可以扩展宏1.1以支持派生属性本身的参数。 总的来说,这似乎是一件好事,并且可能在短期内被用于解决缺乏控制属性的问题。

同上@jimmycuadra@sfackler ,属性对Serde而言比@nrc的注释更合理。 几乎所有不带属性的用例也将由rustc-serialize服务。 如果我们想在晚上让人们下车,我们可以通过弃用Serde来支持rustc-serialize来实现。

我对此处的正确举动尚无定论,但我知道,如果我们在没有属性的情况下稳定下来,我将强烈考虑解析文档注释以提取属性。 我想很多人都不想仅仅为了编写脚本而处理构建脚本:

#[serde(skip_serializing)]

.. 代替:

/// <!-- serde(skip_serializing) -->

@tomaka

在我看来,自定义派生的用例之一是以安全的方式实现标记为不安全的特征,例如,必须准确描述其实现结构的成员布局的特征。

您如何看待在这方面的

我想澄清一件事:

人们使用定制派生主要功能是删除定制属性吗?

我知道在某种类型的上下文中有一些黑客可以进行foo!扩展(例如@sgrif在柴油中提到了这样一个用例,我想@tomaka也提到了一个用例)-这些用例有多重要?

我问的原因是这样的:我看到使派生机制仅返回add'l impls列表的好处。 特别是,这意味着类型声明本身的跨度将是正确的。 向上下文中添加快捷方式API,使您可以在该类型的上下文中向白名单提供属性名称列表,这似乎是对属性的足够简单的修复,我们可以始终弃用它。

但是,如果我们希望启用更多用例(坦率地说,当您考虑一个派生的期望时,这些用例会更多……令人惊讶),那么这将行不通。 在那种情况下,我可能会保持稳定,并计划将来不推荐使用“从原始字节” API,而倾向于使用更好的方法来编写“派生”(毕竟,我们并不真正希望人们会还是使用原始字节,对吗?而是令牌流?)我们可以给API一个稍微笨重的名称=)

@nikomatsakis主要需求不是删除而是忽略属性。 我不知道忽略它们的严重弊端。 例如,通过主属性的附加参数在派生函数上提供白名单,应足以满足真正的派生而不是程序宏hack的所有实际需求。

是的,我在这两种方法之间陷入了困境,两种方法都有明显的缺点:

  1. “简单派生”方法-调整API以仅生成其他项,使用户使用自定义属性作为派生的一部分仍然不稳定,并关闭了柴油和其他包装箱正在跳跃以获得任意程序宏的漏洞。
  2. “计划中的过时”方法-保持API不变,进行细微调整(如Niko提到的命名),以期有一天不推荐使用。 这将要求所有自定义派生作者有一天重写他们的代码,并允许在过渡期间出现意外行为。

编辑:但是@eddyb的白名单听起来也很有希望。

我的意思是,这并不是@nrc提出的建议有很大不同(尽管IIRC他正在谈论用户箱中的白名单),而当您拥有的只是令牌流时,谈论“将使用的标记属性”变得很愚蠢。

我一直在rust-cpp中滥用宏1.1系统的任意过程宏都可以通过另一种方法完全实现,该方法仅提供添加impls和忽略属性的功能。

我确实认为具有忽略属性的能力是必不可少的,但除此之外,我不能超出该范围就不能修改该结构。

我不确定@sgrif在deisel中使用了哪种形式的骇客,在无法修改结构本身而只能添加其他项的世界中,这不是_possible_可以做到的。

@sfackler所建议,允许自定义派生接受参数,这可能是一种使每个人都获得所需功能的方法,同时允许推迟对自定义属性的任何决定。 它看起来不那么好看也不可读,但是可以完成工作。 例如:

#[derive(Debug, Clone, Serialize(field("bar", rename = "baz")))]
pub struct Foo {
  pub bar: String,
}

一旦达成关于自定义属性的决定,就可以不赞成使用此表单。

Serde和我的箱子都需要字段/变量属性。 尝试使用派生参数来模拟这一点没有多大意义,我们需要属性。

无论我们做出什么决定来稳定它,如果/当我们切换到宏2.0派生api时,用户的自定义派生宏都不需要修改其代码(这显然是自定义派生宏的作者会做的)。 似乎最明智的决定是为编译器提供要为您的派生剥离的属性列表,并且至关重要的是,这些属性仅在_all_派生宏运行后才被剥离。 Serde,柴油和我的箱子都存在在多个派生宏上要求相同属性的问题。

有了剥离行为,我不需要@dtolnay制作的后扩展板条箱来添加另一个

FWIW删除这些属性的唯一原因是使HIR保持更薄-就其他所有方面而言,您只需要将它们标记为已使用即可。

人们使用定制派生的主要功能是删除定制属性吗?

我知道有一些技巧可以做foo! 在某种类型的上下文中进行扩展(例如, @ sgrif在柴油中提到了这样一个用例,我想@tomaka也提到了一个用例)-这些用例有多重要?

我完全同意定制派生不能修改结构的事实。

我经常抱怨稳定的Rust中缺少插件,因此当我看到自定义派生时,我将它们用作拥有插件的机会。 但是我显然不会争辩说自定义派生必须支持他们并非为之设计的东西。

似乎最新的每晚都有回归。 得到错误

Queryable是派生模式

在编译我们的示例时。

#[derive(Queryable)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

@sgrif这是由#37198引起的,该值更改了自定义派生,使其使用与其他宏相同的名称空间(以便bang!()宏, #[attribute]宏和#[derive(custom)]宏都共享相同的名称空间)。

在该示例中, #[macro_use] extern crate diesel;导入名为Queryable的bang宏, #[macro_use] extern crate diesel_codegen;导入也命名为Queryable的自定义派生宏,该宏将无声地覆盖bang宏(旁边- #[macro_use]静默覆盖并不理想,但是一旦我们可以用use而不是#[macro_use]从外部包装箱中导入宏,那将不是问题!)。

我认为该错误是由于自定义派生扩展中的bang宏调用Queryable!();引起的,该宏解析为自diesel_codegen的自定义派生,而不是来自diesel的bang宏。

@jseyfried为什么对所有三个“种类”的宏都使用单个名称空间? 在我看来,将属性和bang宏视为占用相同的名称空间似乎没有任何意义,比将函数和类型视为占用相同的名称空间(派生宏更是如此)更有意义。

@withoutboats我认为这是一个错误的类比,我更喜欢认为不同种类的宏与不同种类的值或类型(例如,函数和const)的共享方式相同。 拥有名称空间会带来复杂性成本,我相信拥有的名称空间越少越好,所以问题应该是为什么我们需要不同的名称空间? 显然,对于反向兼容,我们需要宏位于与函数和类型不同的名称空间中。

真正的问题是您不能使用use有选择地导入或重命名宏。 因此,板条箱用户无法解决宏名称冲突。 同时,我不希望简单地导入ProcMacro板条箱以与本地宏名称发生冲突-我以为衍生宏以derive_开头?

拥有名称空间会带来复杂性成本,我相信拥有的名称空间越少越好,所以问题应该是为什么我们需要不同的名称空间?

我认为项目只有在彼此可能模棱两可的情况下才应共享一个命名空间。 const和fns在同一名称空间中,因为它们都在表达式上下文中用作标识。 由于特征对象的语法,类型和特征位于同一名称空间中。

这三种宏都以不同的方式调用。 对我来说,他们应该共享一个命名空间是没有意义的,因为调用永远不会有歧义。

尽管存在实现复杂性成本,但我认为通过单独命名它们的间距不会在_use_复杂性中产生重大成本。 当我考虑语言功能的复杂性成本时,通常会考虑使用复杂性。


您是否会说_all_项应该在同一个命名空间中,如果这不是重大更改? 试图澄清您的思考过程。

反映出更多一点,我可以说所有项目都应该只有1个命名空间-我什至喜欢它。 整个“真正的构造函数”模式有点使IMO迷惑-但这不是Rust为非宏项目所做出的决定,因此对我来说,看起来宏项目占据不同的命名空间似乎更加一致和期望。

真正的问题是您不能使用use来选择性地导入或重命名宏。

您将能够使用宏2.0。 这里的模型基本上是一个权宜之计。

我以为衍生巨集是从generate_开始的?

那是旧的自定义派生系统,我不认为宏1.1可以做到这一点。

尽管存在实现复杂性成本,但我认为使用复杂性不会产生重大成本

定义使用需要付出代价-特别是对于工具而言,使工作变得更加艰辛(尽管可能会争论不休)。 我还认为,实现的复杂性与其说是语言的复杂性无关紧要(尽管名称空间肯定会使编译器复杂化,但我同意这并不重要),但用户必须考虑使用Rust的原因,并且具有敲门砖的功能。对卫生和阴影等事物的影响,使事情进一步复杂化。

如果不是一项重大更改,您是否会说所有项目都应位于同一命名空间中? 试图澄清您的思考过程。

我不会走得太远-我认为将值和类型分开是有好处的,但是可以肯定的是,具有单个名称空间的语言在许多方面都可以更好地工作。

但这不是Rust对非宏项目做出的决定

对点:模块和字段位于值名称空间中,尽管(我认为)它们可以使用的位置与其他值可以使用的位置不同。

@keeperofdakeys

真正的问题是您不能使用use选择性地导入或重命名宏

我们将很快(〜1周)从功能门后面的extern crate s中创建use宏,请参见https://github.com/rust-lang/rfcs/pull/1561和#35896。 我们可能会决定通过自定义衍生板条箱以及自定义衍生板条来稳定use ing宏。

我以为衍生巨集以derive_

对于旧式自定义派生而言,这是正确的。 使用宏1.1自定义派生,例如, #[derive(Serialize)]期望Serialize解析为自定义派生宏。

@withoutboats

对所有三个“种类”的宏使用单一名称空间的原因是什么?

  • 优先顺序:爆炸宏和属性宏已经共享一个名称空间很长时间了,因此在我认为自定义派生所做的更改之前,我也应该共享该名称空间。
  • 向后兼容性:如果我们从单个宏命名空间开始,则可以将其向后兼容。 如果我们从多个宏命名空间开始,那么我们将永远被它们困住。
  • 简单性:如果每个宏“种类”都有自己的命名空间,那么我们将共有五个命名空间。 因此,在https://github.com/rust-lang/rfcs/pull/1561登陆后, use foo::bar;可以导入名为bar五个单独项目(值,类型,爆炸宏)等),就没有简单的方法,例如,重新导出bang宏而不是自定义衍生宏,或者只是将自定义衍生宏导入为baz

向后兼容性:如果我们从单个宏命名空间开始,则可以将其向后兼容。 如果我们从多个宏命名空间开始,那么我们将永远被它们困住。

这令人信服,尤其是因为1.1应该是权宜之计。 :+1:

此中断代码是否适用于具有macro_rules宏(例如PartialEq!任何人?

不, PartialEq今天不在宏命名空间中定义,因为它不是自定义派生。
#[derive(Foo)]首先查找Foo是否为“内置派生”( PartialEqCopy等),然后再查找自定义派生Foo在宏命名空间中。 内建函数被硬编码到derive的定义中。

话虽如此,如果我们最终决定使PartialEq成为前奏中的自定义派生变量而不是内置函数,那么我们可能会破坏您所描述的代码,因此对于将来的证明可能是个好主意。

关于属性问题的建议(假设我们在一个模型上工作,其中派生宏不能修改声明它们的项目,只能修饰):

  • 在后台,自定义派生宏实现了一个特征,当前MultiItemModifier 。 按照计划,宏2.0将继续实现特性,并且将该特性用于该机制的可扩展性。 作为宏的带注释的函数实现了此特征。 尽管我们不使用插件注册器,但这实际上是一个废话。
  • 我们应该专门为宏1.1自定义派生拆分CustomDerive特性。 在通常情况下,宏作者从未见过。 但是他们可以选择直接实现特征,而不是在函数上使用属性(我希望我们可以在impl上重用该属性,也许我们需要讨论这种注册机制)。
  • 我们将declare_attributes函数添加到CustomDerive ,该函数返回Vec<String> 。 它具有默认的隐式返回空vec。
  • 如果宏作者实现了此方法,则任何完全以返回的字符串之一命名的属性都被视为属于该宏。 任何此类属性都不会像宏一样查找,并且不会触发自定义属性lint。 通过派生扩展将属性保留在代码中,但将其标记为已使用。 派生扩展未触及的任何此类属性仍会触发未使用的属性棉绒(但不会触发自定义属性棉绒)。
  • 将来我们可能会引入包括自定义派生类在内的宏可以使用的作用域属性,这些属性将是declare_attributes附加元素,并且不建议使用此机制。
  • 备选:将declare_attributes返回的字符串作为属性的路径前缀进行检查,例如,如果declare_attributes返回了vec!["foo::bar"] ,则#[foo::bar::baz]#[foo::bar::qux]将被允许​​。

有什么想法吗? 抄送@alexcrichton @jseyfried @dtolnay @sgrif @erickt @ rust-lang / lang

@nrc该机制是否允许意外或故意禁用_used_属性(如#[cfg(..)] ? 可以改变吗?

不幸的是, @ nrc我无法完全理解实现在这些约束条件下的外观,因此我不确定它是否可行。

话虽这么说,我对特质和特质对象是否适用还是有些怀疑,因为要在哪里创建特质的实例?

@nrc为什么必须这样做,而不能只是指定派生扩展器功能的属性中的列表? 例如#[proc_macro_derive(Serialize, uses_attrs(serde_foo, serde_bar))]或类似的东西。

@ colin-kiegel我以为它只能在派生应用程序的范围内应用,在这种情况下,禁用其他属性可能是预期的行为,尽管我认为我们可能在宏扩展之前应用cfg(这已经改变了,但我不记得了之前与之后)。

@alexcrichton

话虽这么说,我对特质和特质对象是否适用还是有些怀疑,因为要在哪里创建特质的实例?

嗯,好点,我想我们需要针对宏2.0解决这个问题

@eddyb这是一个好主意,短期内可能更容易做到。 我之所以偏爱命令式方法,是因为它是一种通用的可扩展性机制,我们希望保留这种机制,而将属性添加到属性中并不能很好地扩展,这可能不是我们永远想要坚持的东西。 。 另一方面,现在做起来肯定比较容易,而且闲逛似乎也不是一件坏事,所以我认为这可能是一个更好的选择。

被赶上了-我去了国外然后生病了。 我想重新访问https://github.com/rust-lang/rust/pull/37198 ,因为我认为对于所有种类的宏来说,占用同一个命名空间是没有道理的。 我至少希望自定义派生宏在该命名空间中为derive_Foo或类似名称。 即使在标准库中,也存在使用相同名称的多种类型的宏的使用案例(例如#[cfg]cfg!

我还发现共享名称空间有点尴尬。 从最终用户的角度来看,似乎似乎存在某种可互换性,歧义性或有趣的事情,而事实并非如此。 我认为像这样的“随机”限制可能会使将Rust理解为一门语言变得更加困难。

但是,我认为这可能不是世界末日。 看起来将来仍可以引入不同的名称空间,而不会造成太大的影响。

@sgrif @ https://github.com/rust-lang/rust/issues/35900#issuecomment -256247659中描述了单个宏名称空间的基本原理。

我认为所有种类的宏都占用同一个命名空间是没有道理的

需要明确的是,爆炸宏和属性宏始终占用相同的名称空间; #37198刚刚将自定义派生到此命名空间中。

即使在标准库中,也存在使用相同名称的多种类型的宏的使用案例(例如#[cfg]cfg!

另外,也可以将cfg视为可以通过爆炸调用或属性调用使用的单个宏(尽管如今用户无法定义可以通过爆炸或属性调用的宏,我们可能会决定在宏2.0中允许它。

从最终用户的角度来看,它似乎似乎具有某种可互换性

我认为当两个宏发生冲突时,可以使用清晰的错误消息来改善这种情况(我将继续改进今天的消息)。

看起来将来仍可以引入不同的名称空间,而不会造成太大的影响

我相信拆分宏名称空间是完全向后兼容的(如果我错了,请纠正我)。 这是我今天保留单个宏名称空间的主要动机-我们希望宏1.1尽可能与将来兼容。

最后,我认为bang / attribute宏与custom派生宏之间的冲突在实践中很少见,因为bang / attribute宏通常为小写,而custom派生通常为大写。 换句话说,当遵循这些命名约定时,custom派生已具有其自己的命名空间。

看来破损(https://github.com/diesel-rs/diesel/issues/485)是由支持引起的,例如Queryable! { struct S; }以及#[derive(Queryable)] struct S; 。 一旦自定义派生稳定下来,就不需要支持Queryable! { struct S; }所以这不再是问题了,对吗?

同时,我相信我们可以更新diesel以便

  • Queryable!仍然不支持#[macro_use] extern crate diesel_codegen; ,和
  • #[derive(Queryable)] ,而不是Queryable! ,支持与#[macro_use] extern crate diesel_codegen;

随时在IRC上对我进行ping讨论-我很乐意编写PR。

@nrc @alexcrichton

话虽这么说,我对特质和特质对象是否适用还是有些怀疑,因为要在哪里创建特质的实例?

我同意@eddyb的观点,如果我们只想处理属性,为了方便和方便,我们应该扩展属性。

但是,如果我们想要一个更灵活的扩展系统,那么我想象我们将使用特征,但是这些特征将被定义为不引用Self的方法的集合:

trait CustomDerive {
     fn foo(); // note: no self
     fn bar(); // again, no self
}

然后,您可以在虚拟类型上实现它,例如struct my_annotation_type; (我想与属性共享相同的名称),然后我们将使用特征解析来提取相关函数,例如<my_annotation as CustomDerive>::foo (可能我写出元数据时)。 关键是,我们永远不会(或不需要) my_annotation的实例,它只是一种将一系列相关功能集中在一起的分组机制。

当然,这不是有史以来最优雅的事情,但是我不确定是否有更好的方法? 我认为,这正是我们要从属性函数开始的原因。 =)

关于命名空间, @ sgrif提供了#[cfg]cfg!示例的好例子。 我当然可以想象一个人想要#[derive(SomeTrait)]并且也有一些像SomeTrait! { ... }这样的宏...可以做什么。 =)但是@jseyfried也具有向后兼容性的好例子-只要我们没有达到_already_的限制。

我倾向于默认使用较少的名称空间,这主要是因为我认为将值/类型名称空间拆分给我们带来了痛苦。 就是说,我认为大多数已知的痛点在这里并不适用:

  • 类型/值拆分在名称解析中很麻烦,因为use foo::bar可能会或可能不会导入名为bar的模块,因此可能(也可能不)与bar::baz这类名称解析相关
  • 对于泛型而言,类型/值拆分对泛型而言是一种痛苦,尽管这种痛苦也归因于类型和值之间的句法拆分。

但是在很大程度上,由于我们已经跨过了桥梁,所以我不确定让“自定义派生”存在于其自己的名称空间中会带来任何特殊的挑战吗?

我们可以在生成的宏中添加derive_前缀,以使其具有伪命名空间。 如果我们想做出改变,现在是时候了。

@keeperofdakeys这是否不等于自定义派生作者决定命名其自定义派生derive_* ? 无论如何,我认为derive_*名称对于最终用户来说太难看了。

@nikomatsakis

但是在很大程度上,由于我们已经跨过了桥梁,所以我不确定让“自定义派生”存在于其自己的名称空间中会带来任何特殊的挑战吗?

导入解析算法可以处理任意多个名称空间,但是它对每个名称空间的工作量可能都不大。 特别是,对于每个导入I和每个未使用的命名空间S ,它证明了I的解析度在S失败(参见https://github.com。 com / rust-lang / rfcs / pull / 1560#issuecomment-209119266)。 该证明通常需要使用全局导入图的DFS(其中顶点是模块,而边缘是全局导入)来搜索相关的不确定的导入。

但是,这种额外的按名称空间的工作可能不会在实践中有所作为,并且可以在需要时避免使用(参见https://github.com/rust-lang/rfcs/pull/1560#issuecomment-209119266)次要限制,仅适用于宏名称空间。

我在#37198中合并了名称空间,以使大多数选项保持打开状态,因为我认为在实践中这不会受到限制。 如果人们今天想要它,并且@ rust-lang / lang永远拥有多个宏命名空间是可以的,那么我没有异议。

@nikomatsakis

我认为是的,尽管很幸运,您的策略仍会奏效。 我个人的感觉是,奇怪并不能减轻它的负担(例如,我今天拥有的东西对我来说很好),但是无论哪种方式都应该可以实现。

就像我以前写过的@alexcrichton一样,我很高兴等到我们需要更多的功能(如果有的话)之后,我们才能做类似我描述的特质的事情,或者做一些更好的事情。 就目前而言,我认为仅扩展应用于fn的属性就足够了。

我很好奇,并开始了这个想法的基本实现(在proc_macro函数上使用属性来表示要标记为在项目上使用的属性的名称)。

我已经清除了FCP标签,因为看来我们还没有_quite_达到我们准备稳定的地步。 我将尽力总结对话的状态,特别是在我们仍然需要坚定的决定和/或代码贡献的地方突出显示错误:

问题1:自定义宏应该能够对它们要遍历的项进行突变吗?

  • 没有人认为他们应该在实践中这样做
  • 这是另一种模式,最初被YAGNI拒绝
  • 更改字段名等的派生类组合不佳,使得应用程序顺序可见;
    可以通过以下两种方法减轻这种危险:
  • 另一方面,如果说自定义派生只需要返回新的impls

    • 跨度信息更好

    • 自定义派生更容易编写

  • 但是,许多自定义派生使用自定义属性来指导其扩展,这些属性会生成警告/错误

    • 当前的技术是将它们从AST中删除

  • _也:_我们想尽快将这些东西发布出去,不想做长时间的实验或大刀阔斧的改变

提案:

  • 保持一切不变,也许以后不推荐使用

    • 权宜之计,有点不幸

  • 使用白名单属性扩展#[proc_macro] ,让rustc认为它们“已使用”并忽略它们

问题2:custom派生类是否应该与其他宏共享相同的名称空间?

争论:

反对:

提案:

  • 拆分为“自定义派生”名称空间
  • 保持现状

其他事情?

还有其他未解决的问题吗?

突变问题的潜在解决方案:

它们不是类型TokenStream -> TokenStream的自定义派生类,而是类型为
Item -> TokenStream ,其中Item是不透明类型,仅以两种方法开始:

  • item.tokens() ,它返回今天将要传递的TokenStream ,以及
  • item.use_attrs(name) ,它将使用给定名称标记所有属性。

返回的TokenStream仅包含派生的impl
我们最终可以在Item的API中添加便捷功能(例如,迭代项目的字段/变量)或更高级别的派生API,例如syntax_ext::deriving::generic

我很乐意#[proc_macro_derive]实现Item -> TokenStream提案。

我认为稳定可能导致基础项目变异的自定义衍生产品将是一个错误。 我们通过允许突变而牺牲的跨度信息已经引起了问题-例如,我们必须在固定#37563和固定#36218之间选择。

我真的没有看到强制性白名单的吸引力,有人可以提出用例吗?

我不确定,是否有意/希望此代码编译:

#![feature(proc_macro)]

#[proc_macro_derive(Whatever)]
struct Foo {}

@eddyb我不确定命令式白名单是否具有内在吸引力-我认为这样做的好处
Item -> TokenStream是扩展Item的API比添加更多种类的声明性属性容易。 话虽这么说,再想一想,可能没有很多其他用例需要Item超过TokenStream ,所以TokenStream -> TokenStream带有声明性白名单的

宏2.0会取代此api吗? 如果是这样,可扩展性就没有什么真正的问题,而且Item -> TokenStream api似乎不太引人注目。

@keeperofdakeys根据RFC:

总体而言,这应被视为朝着正式的“宏2.0”迈进的一步。

宏1.1的意图是在本质和实现上尽可能与宏2.0接近,而不会稳定大量功能。 从这个意义上说,这样做的目的是给定稳定的宏1.1,我们可以向后兼容地层叠要素,以获取宏2.0。

https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233449053和https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233708395和https:// github。 com / rust-lang / rfcs / pull / 1681#issuecomment -239558657似乎指示在宏2.0到来时仅预期“有限”弃用。 特别是:“如果不实际使用字符串化令牌树的模式,将不受欢迎”。

@eddyb

我真的没有看到强制性白名单的吸引力,有人可以提出用例吗?

好吧,我可以想象这样一种情况,其中属性名称是以某种方式动态生成的(也许是基于类型或字段的名称?),因此无法提前声明它们。 虽然似乎是人为的,但不是特别好的做法。

我发现@jseyfried的另一种吸引力-不是因为标记使用属性的命令性,而是因为随着时间的流逝, Item API可能会变得更丰富。 也就是说,我们可能支持以一种非常结构化的方式遍历字段集,提供对我们可能拥有的更多数据(例如名称解析)的访问等。

当然,其中一些也将(某种程度上)来自某种形式的标准AST API。

关于时间的说明:我真的非常希望看到Macros 1.1在下一个周期中变得稳定。 我们被阻止的东西最终感觉还很小。

本着这种精神,我对我描述的问题:

  1. 我对声明性的#[proc_macro]扩展名_或_将类型更改为Item感到满意。 我还认为,无论哪种选择,我们都可以在将来采用其他方法,如果这是个好主意。 我有点想和任何先实现的人一起去。 =)
  2. 关于多个名称空间,我认为我们现在应该将它们保留在相同的名称空间中。 对我而言,向后兼容性(即保持我们的选择开放)与大写字母已经将“自定义派生”宏与实际中的其他事物区分开的事实的结合是令人信服的。 区分#[cfg]cfg!感觉我们可以用其他方式处理,或者,如果需要,我们可以引入拆分的命名空间_then_。 似乎没有一个统一的名称空间尤其会损害自定义派生用例,这是我们谈论稳定的唯一事情。 对?

@nikomatsakis您的总结听起来很准确,感谢您编写! 我很遗憾我们不会在Rust 1.14中获得宏1.1,但是我知道这些都是有争议的问题。

我个人的感觉仍然是要稳定一切,因为自定义派生必须剥离属性,并且只有一个命名空间。

我不太遵循Item -> TokenStream API,返回的令牌流是否仍包含原始项目或仅包含添加的impls? 这是否意味着应该花费&mut Item

@ est31您的评论听起来像是个错误,您可以为此打开一个单独的问题吗?

我几乎完全同意@nikomatsakis的评论。 我认为,重要的是没有自由统治权来改变他们所附加的项目,但是所有提议的实现对我来说似乎都很好。

似乎没有一个统一的名称空间尤其会损害自定义派生用例,这是我们谈论稳定的唯一事情。 对?

之所以出现这个问题,是因为它损坏了柴油,该柴油当前具有称为Queryable!宏,您可以在其中包装一个结构以派生Queryable

作为现在既不使用也不维护柴油的人(即不受我将要提出的建议:sweat_smile:影响的人),我认为最好的办法是现在保留1个名称空间,并让柴油更改名称。这些宏到derive_Queryable!或类似的东西。 无论如何只要稳定,它们就会被弃用,对吧?

我为建议的功能创建了PR https://github.com/rust-lang/rust/pull/37614 。 它使用一个单独的属性#[proc_macro_attributes(..)] ,您不再需要返回返回的TokenStream

我提交了#37637,以弄清楚proc宏应如何处理$crate

只是为了澄清一下,该用例被认为是正确的还是对系统的滥用:

我在这里基于现有结构和属性生成新结构,我非常喜欢该设计,因为它允许我将事物整合为一个结构。

但是,如果以后可能要更改系统以禁止这种实现,我可能现在就停下来而不是花更多的精力(当前实现只是概念上的快速证明)。

@TheNeikos

向后不兼容的主要变化是您不能再修改该项目(您不将其传递回TokenStream)。 我想说,除了impl之外,别无其他目的,但是没有什么可以阻止您这样做。 您只需要注意名称冲突。

另一个主要更改是能够提供属性名称列表,这些属性名称不应触发该项目上的自定义属性错误。

RE:Diesel中的名称空间冲突。 我不确定一旦该功能稳定后是否要弃用这些宏,这取决于某些人是否仍然希望免费使用编译器扩展。 令人怀疑。 虽然对于内部测试还是有用的,但是重命名就可以了。

我确实很遗憾失去了修改输入流的能力。 能够删除带有注释的项目也使我们在该系统中也具有bang宏。 我了解这些问题,但是在这些问题上失去这种能力真是可惜。

@TheNeikos @sgrif我的观点是,任何严重修改输入的内容都不是派生的,因此此处无意论述。 我认为将自定义派生用于通用proc宏有些危险(缺乏卫生习惯等),因此我不鼓励这样做。

无法修改项目意味着保留项目的跨度信息,这使项目上的错误消息更加清晰(这也意味着派生输出中的错误消息不再指向项目的跨度,而是指向派生属性的跨度)。 如果我们真的只想要适当的过程宏,我看不出有什么理由让人们滥用此功能。

@TheNeikos我不认为我们不会禁止通过派生生成新结构,只要您不对要派生的结构进行主题化即可。

我认为这是惯用的; 我认为生成新类型很好,但是如果这些类型是与您派生的特征相关联的类型,那就更好了。 例如,假设我们能够为某个类型导出IntoIterator -这将涉及创建Iterator结构。 从概念上讲,您应该派生此类型的行为,但该行为可能不是“特质暗示”。

@sgrif

我确实很遗憾失去了修改输入流的能力。 能够删除带有注释的项目也使我们在该系统中也具有bang宏。 我了解这些问题,但是在这些问题上失去这种能力真是可惜。

嗯,我绝对是同情的,尽管很明显(如@nrc所指出的),这不是系统设计的目的,并且它倾向于暴露各种粗糙的边缘。 在您的用例中,这可能并不重要。 我想一个关键的问题是,我们如何迅速进行“爆炸宏1.1”之类的事情。

PR已被合并,因此您应该能够在第二天晚上看到以下更改。 proc_macro_derive函数应该不再返回该项目(这样做将触发有关两次定义类型的错误),并且您现在可以将属性名称列表提供给白名单,例如#[proc_macro_derive(Derive, attributes(Foo, Bar)]

cc @dtolnay@sgrif ^

不幸的是很快会造成破损

是的,我提交了https://github.com/serde-rs/serde/issues/614进行跟踪。

我想我已经在Diesel中修复了https://github.com/diesel-rs/diesel/pull/493中的损坏,可以肯定的是,一旦夜间再次建造,就可以了。

因此,如果我正确地读取了此线程,由于proc宏只能发出附加到初始项目的额外项目,因此它也不能为初始项目添加更多注释吗? (我提到了允许使用“同级注释”的内容,但除此之外没有其他内容。)

我的(微小的,未发布的)板条箱中有一个#[derive(newtype)] proc宏,该宏根据其注释的结构扩展为其他#[derive()]的不同集合。 例如, #[derive(newtype)] struct Foo(u64)扩展为#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] struct Foo(u64);#[derive(newtype)] struct Foo(::semver::VersionReq)扩展为#[derive(Clone, Debug, PartialEq)] struct Foo(::semver::VersionReq); 。 因此,该宏不会修改struct成员,但会向其添加其他派生(它们也不会修改struct)。

扩展此宏后,有几十个这样的结构,每个结构都有十个左右的新派生类。 我喜欢这样,如果我意识到我希望所有u64类型都实现一个更多的特征,我可以只在newtype宏代码中的某个位置而不是每个结构上更改一组派生。

我曾经有一个macro_rules newtype!宏,但是切换到proc宏是因为:

  • 是否需要文档注释,是否存在pub等等都可以免费获得,而无需组合数量的宏匹配臂。
  • 即使我编写了组合宏匹配臂,也很难找到它们彼此不冲突的顺序。

不幸的是,不能,您不能再像以前那样这样做。

关于此功能将来的兼容性是稳定的:由于不需要将插件功能“纯”,如果将来分配给该功能的对象的顺序发生更改,或者如果rustc实现多线程,这将是一个重大更改。编译?

@ est31如果有时间,我们应该尝试实现已经提到的IPC隔离。

在最近的更改之后,我一直在柴油中看到ICE。

../src/librustc_metadata/decoder.rs:490:条目:找不到ID:编号为28的板条箱“ diesel_codegen”中的DefIndex(1)

@sgrif ,将是问题#37788,将由问题#37793修复(希望它会在明天的晚上...结束)。

@ est31

https://github.com/rust-lang/rust/issues/37839是使用本身使用proc_macro箱子的lib箱子的问题。 AFAICT所有测试均不受此影响,因为它们要么仅编译proc宏模块,要么编译proc宏模块和直接引用proc宏的bin模块。

编辑:现在已修复!

@Arnavion您在#37839中看到的问题已修复,可解决常规并发症,但在使用--target时仍然存在问题,如#37958中所述。 我提供了一个使用--target

@rfcbot fcp合并

现在已经实现了白名单属性功能,我认为我们应该对此进行稳定! Serde,Diesel和其他用户-如果当前设计不适合您,现在是您对对象的更改。 =)

抄送@sgrif @erickt

团队成员@nikomatsakis已提议将其合并。 下一步是由其他带标签的团队审核:

  • [x] @alexcrichton
  • [x] @aturon
  • [x] @brson
  • [x] @eddyb
  • [x] @japaric
  • [x] @michaelwoerister
  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @pnkfelix
  • [x] @vadimcn
  • [x] @withoutboats
  • [x] @wycats

当前没有问题。

一旦这些评论者达成共识,这将进入其最终评论期。 如果您发现在此过程中尚未提出的重大问题,请大声说出来!

请参阅本文档,以获取有关带标签的团队成员可以给我哪些命令的信息。

我很乐意在不久的将来探索爆炸宏。 虽然没有异议。

@rfcbot评论

当前,如果TokenStream解析失败,则返回一个空的LexError 。 是否可以在此处返回更好的错误消息,例如哪个令牌解析失败? 尽管您图书馆的用户可能不想看到这些错误消息。

是的,这对于宏作者来说很方便。 我不得不求助于将流写入文件并在操场上查看以发现错误。

我认为,如果仅针对宏提交更好的错误报告,用户也会从中受益。

https://github.com/rust-lang/rust/pull/38140上,我们强制将自定义派生声明公开。 这种理论是,我们可能有一天想要私有派生,然后我们可能希望基于定义函数的可见性进行区分。 无论如何,这是保守的选择。 我想我可以将其浸泡一两天,以便人们在合并之前看到它,因为我们正在稳定此功能。

#37480已关闭,应在清单中反映出来。

固定

@pnkfelix我很

:bell:根据上面的评论

psst @nikomatsakis ,我无法添加final-comment-period标签,请这样做。

即将进行的稳定化是否假定将首先修复顶部清单中其余的已知错误? 还是那些没有阻碍稳定的人?

现在有两个:

  • #36935评论说它已解决。
  • #36691似乎对我没有阻碍,我们可以在将来某一天允许它们扩展为mod foo; ,只要我们愿意而不会破坏我所相信的任何东西。

嘿! 这是RFC 1636文档审核。 自从RFC 1636被接受以来,这是第一个稳定的主要功能,在这种情况下,我们应该做得很好。

RFC指出:

在稳定功能之前,现在将记录以下功能:

  • 语言特点:

    • 必须在Rust参考中记录。

    • 应该用Rust编程语言记录。

    • 可以在Rust实例中进行记录。

  • 语言功能和标准库更改都必须包括:

    • 更改日志的一行

    • 有关长版本发布公告的较长摘要。

正确的过程是什么? 我们应该在此问题的顶部评论中添加清单项目,还是为跟踪文档创建新问题? 在我看来,在1.15版之前,我们应该拥有符合这些要求的文档。

抄送@ rust-lang / docs

谢谢@withoutboats ! 是的,这是第一个主要课程。 今天早上我把它放在清单上看,瞧瞧,你击败了我😄

我的想象始终是稳定的决定和实际的稳定是分开的。 也就是说,@ rust-lang / lang可以说“可以使其稳定”,但是删除该门的提交也可以确保文档存在。 在存在不稳定书籍的世界中,这会将文档从那里拉到稳定文档中。 但是直到那时,事情还是有些尴尬。

鉴于我们刚刚发布了版本,我的计划是执行以下操作:

  1. 等待这个离开FCP
  2. 获取一些文档。 (我打算在这种情况下编写它们)
  3. 使稳定PR。

可能合并两个和三个。

/ cc @ rust-lang / core,因为这是跨团队的问题。

@steveklabnik对我来说听起来不错,我也可以在任何时候登陆文档(甚至比FCP提前完成)。 无论如何,这里的FCP都是伪完成的,因为我们不小心花了很长时间才进入。

迅速将它们引入也将是一件好事,这样我们可以确保安全地移植到1.15 beta分支!

如果我是第一个遇到此问题的人,那可不是什么坏事,但让我们将其包含在已知的错误中:在具有自定义派生的结构内使用类型宏会导致ICE https://github.com/rust-lang/锈/问题/ 38706

https://github.com/rust-lang/rust/pull/38737修复了类型宏ICE: heart:。 有机会将其反向移植到测试版吗? 原理:两项突出的新功能(一起在1.13中发布和在1.15中发布)在将它们一起使用时会使编译器崩溃,这似乎很糟糕。

我刚刚创建了关于proc_macro板条箱文档的#38749。

我已经多次阅读过Macros 1.1会稳定在1.15的情况,但是两周前发布的1.15.0-beta.1以及至少extern crate proc_macro;仍然在其中以及夜间4ecc85beb 2016 -12-28。 该计划是否支持稳定更改?

@SimonSapin是的,那是计划,但是我们需要实现它!

仍然是计划:p

如果用户写#[derive(Foo)] #[foo_def = "definition.json"] struct MyStruct;则宏处理程序将无法知道“当前目录”是什么,因此找不到definition.json

这是设计使然,因此修复起来并不容易,而且我想现在修复这个问题为时已晚。

您可以转到Span -> FileMap -> filename->目录。 缺少的只是通过proc_macro访问信息。

您还需要告诉编译器将依赖项添加到definition.json以便在修改构建时使其不干净。

proc宏可以使用env::var("CARGO_MANIFEST_DIR")来获取包含该宏调用的条板箱的Cargo.toml目录。 大概foo_def是相对的。 参见https://github.com/dtolnay/syn/issues/70#issuecomment -268895281。

@tomaka可通过突变FileMap,例如做是如何include_str!宏做的。

大概foo_def是相对的。

我认为必须相对于Cargo.toml放置路径并不是很直观。

可以通过更改FileMap来完成,例如include_str! 宏做到了。

是的,我知道可以做到,但是使用当前的程序宏API不能做到。

参数是Item 。 可以添加一种方法来检索该项目的跨度,但这是可以接受的,但是向Item添加方法以要求编译器添加依赖项将是IMO。

您可以转到Span-> FileMap-> filename->目录。

这些API(尤其是FileMap )是否在稳定的路径上?

他们不必如此,实际上我不想稳定任何内部结构。 相反,我们可以稳定提取有关Span (例如,行,列,文件名)的API。

我只是在我的箱子里遇到了这个错误。 这是怎么回事?

`` error: Cannot use #![feature(proc_macro)] and #![feature(custom_attribute)]同时
``

@alexreg如果您使用的是#[derive] ,它现在是稳定的。 您不需要#![feature(proc_macro)]

@alexreg
proc_macro_derive s(1.1宏)现在稳定-您只需删除#![feature(proc_macro)]

#[proc_macro_attribute]最近落在#![feature(proc_macro)]功能门的后面; 这些与#![feature(custom_attribute)]不兼容。 #![feature(custom_attribute)]将被弃用一旦更换土地(https://github.com/rust-lang/rfcs/pull/1755)。

@jseyfried我认为我们应该更改proc_macro上的跟踪问题,因为它已关闭且不包含相关信息。

多谢你们。 这就说得通了。

@abonander是的, #![feature(proc_macro)]现在肯定应该指向#38356 -在检查#38842时,我应该已经验证了这一点。 您可以提交公关吗?

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