Rust: const fn 跟踪问题 (RFC 911)

创建于 2015-04-06  ·  274评论  ·  资料来源: rust-lang/rust

https://github.com/rust-lang/rust/issues/57563 | 新的元跟踪问题

旧内容

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

此问题已关闭,有利于更有针对性的问题:

  • ~局部变量、赋值、解构:rust-lang/rust#48821~
  • 与模式集成: https ://github.com/rust-lang/rust/issues/57240
  • 浮点数: https ://github.com/rust-lang/rust/issues/57241
  • 恐慌: https ://github.com/rust-lang/rust/issues/51999
  • 循环: https ://github.com/rust-lang/rust/issues/52000
  • 控制流程: https ://github.com/rust-lang/rust/issues/49146
  • 比较原始指针: https ://github.com/rust-lang/rust/issues/53020
  • 取消引用原始指针: https ://github.com/rust-lang/rust/issues/51911
  • 指向usize的原始指针: https ://github.com/rust-lang/rust/issues/51910
  • 联合字段访问: https ://github.com/rust-lang/rust/issues/51909
  • 长期运行评估的警告: https ://github.com/rust-lang/rust/issues/49980
  • 持续传播不会导致错误: https ://github.com/rust-lang/rust/issues/28238
  • &mut T参考和借用: https ://github.com/rust-lang/rust/issues/57349

稳定前要做的事情:

CTFE = https://en.wikipedia.org/wiki/Compile_time_function_execution

A-const-eval A-const-fn B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue T-lang

最有用的评论

如果我完全偏离主题,请忽略这一点。

我在这个 RFC 中看到的问题是,作为用户,您必须标记尽可能多的函数const fn ,因为这可能是最佳实践。 目前在 C++ 中使用 conexpr 正在发生同样的事情。 我认为这只是不必要的冗长。

D 没有const fn但它允许在编译时调用任何函数(有一些例外)。

例如

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

请注意,我并不是真正的 Rust 用户,几分钟前我才阅读了 RFC,所以我可能误解了一些东西。

所有274条评论

这是由 #25609 关闭的吗?

@Munksgaard这只是增加了对编译器AFAIK的支持。 stdlib 中有很多函数需要更改为const fn并测试是否损坏。 我不知道这方面的进展如何。

我希望这可以在std::ptr::null()null_mut()上实现,这样我们就可以使用它们来初始化static mut *MyTypeWithDrop而无需诉诸0usize as *mut _

编辑:删除,因为它超出了主题

需要明确的是,这里的问题主要不是关于该功能的有用性,而是关于制定它的最佳方式(或制定它的最佳框架)。 请参阅 RFC 讨论。

现在这是最终稳定的跟踪问题。

https://github.com/rust-lang/rust/issues/29107已关闭。

我不同意“与模式集成”或对标准库的任何更改都应该阻止这一点。 即使没有这些更改,这也非常有用,并且这些更改可以在以后完成。 特别是,我想尽快开始在我自己的代码中使用const fn

因此,是否可以重新评估其稳定状态?

我不怀疑const fn即使是目前的有限形式也会是有用的功能,但我真正想要的是,理想情况下,在沿着这条道路走得更远之前,那些支持“ const fn方法”来思考和表达他们喜欢的结局。 如果我们只是继续以最明显的方式逐步添加看似有用的功能,那么在我看来,我们最终很可能最终会或多或少地复制整个 C++ 的constexpr设计。 这是我们感到舒服的事情吗? 即使我们说是,我更希望我们以清醒的眼光选择这条路,而不是随着时间的推移小步退缩,作为阻力最小的道路,直到它变得不可避免。

(鉴于安全 Rust 代码的语义应该是完全可定义的,似乎最终至少每个不(传递地)依赖于unsafe的函数都应该能够被标记为const . 鉴于unsafe应该是一个实现细节,我敢打赌人们也会推动以某种方式放松这个限制。我宁愿我们看看国外,并试图找到一个更有凝聚力、能力和用于分级和类型级计算的良好集成故事。)

@glaebhoerl

我不怀疑 const fn 即使是目前的有限形式也会是有用的功能,但我真正想要的是,理想情况下,在沿着这条道路走得更远之前,那些支持“const fn 方法”的人考虑并阐明他们喜欢的残局……在我看来,我们最终或多或少地最终会复制整个 C++ 的 constexpr 设计。

我个人更希望的是,我们对如何实现它以及我们将涵盖的语言的哪一部分有一个相当清晰的看法。 也就是说,这与我认为对整数的关联常量或泛型的支持密切相关。

@eddyb和我最近在一个方案上做了一些草图,该方案可以持续评估非常广泛的代码。 基本上将所有常量降低到 MIR 并对其进行解释(在某些情况下,
抽象解释,如果有你还不能评估的泛型,这就是我最感兴趣的地方)。

然而,虽然支持很大一部分“内置语言”似乎相当容易,但实际代码在实践中遇到了非常快速地进行内存分配的需要。 换句话说,您想使用Vec或其他一些容器。 这就是整个口译方案在我看来变得更加复杂的地方。

也就是说, @glaebhoerl ,我也很想听听你阐述喜欢的替代结局。 我认为您在const fn RFC 中勾勒出了一些这样的想法,但我认为在这种情况下再次听到它会很好。 :)

分配的问题是让它逃到运行时。
如果我们可以以某种方式禁止跨越编译时/运行时障碍,那么我相信我们可以使用const fn来实现liballoc #$ 。
管理这些类型的分配并不比处理解释堆栈上的字节寻址值更难。

或者,我们可以生成运行时代码,以在每次必须通过障碍时分配和填充值,尽管我不确定有什么样的用例。

请记住,即使使用成熟的constexpr评估, const fn _would still_ 是纯粹的:在'static数据上运行两次会得到完全相同的结果,并且不会副作用。

@nikomatsakis如果我有一个,我会提到它。 :) 我主要只看到已知的未知数。 作为泛型系统一部分的const的整个事情当然是我理解的 C++ 设计的一部分。 至于有关联的const s 和const泛型参数,考虑到我们已经有固定大小的数组,其中const s 作为其类型的一部分,并且想要抽象如果有更好的——而不是更一般的——做这件事的方法,我会感到惊讶。 事物的const fn部分感觉更加可分离和可变。 很容易想象更进一步并在泛型中有const impl s 和const Trait边界之类的东西,但我_确定_这种通用事物的现有技术已经弄清楚了我们应该设法找到它。

在 Rust 语言的主要用例中,主要需要低级控制的用例,如内核,似乎已经得到了相当好的服务,但 Rust 可能具有很大潜力的另一个领域是主要需要高性能的东西,并且在对分阶段计算( const fn已经是一个非常有限的实例)的空间强大支持(以某种形式)似乎可以改变游戏规则。 (就在最近几周,我遇到了两条不同的推文,这些人决定从 Rust 切换到具有更好的暂存功能的语言。)我不确定是否有任何现有的语言解决方案“接近我们”—— C++ 的constexpr ,D 的 ad-hoc CTFE,我们的程序宏——对于这类事情,真的感觉很鼓舞人心,功能强大/完整。 (过程宏似乎是一件好事,但更多的是用于抽象和 DSL,而不是用于面向性能的代码生成。)

至于什么_会_鼓舞人心和足够好......我还没有看到它,而且我对整个空间还不够熟悉,无法准确地知道在哪里看。 当然,根据上述内容,我们可能至少想看看 Julia 和 Terra,即使它们在很多方面看起来与 Rust 完全不同。 我知道 Oleg Kiselyov 在这方面做了很多有趣的工作。 Tiark Rompf 在LancetLightweight Modular Staging for Scala 方面的工作似乎绝对值得一看。 我记得在某个时候看到过@kmcallister的介绍,关于依赖类型的 Rust 可能是什么样子(这可能至少比到处粘贴const更通用),我还记得从 Oleg 那里看到了一些效果类型本身是一种暂存形式(考虑到编译和运行时之间的阶段分离很像阶段,这感觉很自然)......许多不同方向的许多令人兴奋的潜在联系,这就是为什么它会让人觉得错过了如果我们只致力于我们想到的第一个解决方案,这是一个机会。 :)

(这只是一个脑筋急转弯,我几乎可以肯定地对许多事情进行了不完美的描述。)

然而,虽然支持很大一部分“内置语言”似乎相当容易,但实际代码在实践中遇到了非常快速地进行内存分配的需要。 换句话说,您想使用 Vec 或其他一些容器。 这就是整个口译方案在我看来变得更加复杂的地方。

我不同意“实践中的真实代码”的描述。 我认为人们对 Rust 很感兴趣,因为它有助于减少对堆内存分配的需求。 尤其是我的代码,尽可能地避免堆分配。

能够做更多的事情将是_nice_,但能够使用编译器强制不变量构造非平凡类型的静态实例是必不可少的。 C++ constexpr方法非常有限,但它超出了我的用例所需:我需要提供一个函数,该函数可以构造带有参数xT类型的实例xyz使得函数保证xyz是有效的(例如, x < y && z > 0 ),因此结果可以是static变量,而无需使用在启动时运行的初始化代码。

@briansmith FWIW 另一种有可能解决相同用例的方法是,如果宏具有隐私卫生,我相信(希望)我们计划让它们拥有。

@briansmith FWIW 另一种有可能解决相同用例的方法是,如果宏具有隐私卫生,我相信(希望)我们计划让它们拥有。

我想如果您使用过程宏,那么您可以在编译时评估x < y && z > 0 。 但是,如果程序宏可以在稳定的 Rust 中使用,似乎还需要很多很多个月。 const fn很有趣,因为据我了解情况,它可以启用稳定的 Rust _now_。

@glaebhoerl我不会为了严格的卫生而屏住呼吸,我们很可能会有一个逃生机制(就像真正的 LISP 一样),所以出于任何安全目的你可能不想要它。

@glaebhoerl还有 https://anydsl.github.io/,它甚至使用
类似 Rust 的语法 ;) 它们基本上是针对分阶段计算和
特别局部评价。

2016 年 1 月 16 日星期六下午 6:29,Eduard-Mihai Burtescu <
通知@github.com> 写道:

@glaebhoerl https://github.com/glaebhoerl我不会屏住呼吸
严格的卫生,我们很有可能会有一个逃生机制
(就像真正的 LISP 一样),所以你可能不想要它来保证任何安全
目的。


直接回复此邮件或在 GitHub 上查看
https://github.com/rust-lang/rust/issues/24111#issuecomment -172271960。

29525应该在稳定之前修复

鉴于安全 Rust 代码的语义应该是完全可定义的,似乎最终至少每个不(传递地)依赖于unsafe的函数都应该能够被标记为const . 鉴于unsafe应该是一个实现细节,我敢打赌人们也会推动以某种方式放松该限制。

只是一个想法:如果我们曾经正式定义Rust 的内存模型,那么即使是unsafe代码也可能在编译时通过抽象/符号解释来安全和合理地评估它——也就是说,使用原始指针不会t 变成像在运行时那样的直接内存访问,而是像查找已分配地址的哈希图以及它们的类型和值或类似的东西(仅作为说明的示例),每一步都检查有效性 - 所以任何行为未定义的执行将_严格地_是编译器报告的错误,而不是rustc中的安全漏洞。 (这也可能与在编译时以符号方式或以平台相关方式处理isizeusize的情况有关。)

我不确定const fn会给我们带来什么影响。 一方面,这可能会在编译时打开更多有用的代码—— BoxVecRc 、任何使用unsafe的代码const fn ”的边界现在将基本上向外移动到_任何不涉及 FFI_ 的事物_。 所以我们_实际上_要在类型系统中跟踪,在const ness 的幌子下,什么东西(传递地)依赖于 FFI,什么东西不依赖。 并且无论某些东西是否使用 FFI,_still_ 仍然是人们理所当然地认为是内部实现细节的东西,而且这个限制(不像unsafe )_really_ 似乎并不可行。 在这种情况下,您可能有更多的fn s 有资格获得const ness,而不是那些没有资格的人。

所以你仍然有const围绕一个任意的、暴露实现的限制,你最终还不得不几乎在任何地方都写const 。 听起来也不是很吸引人……

也就是说,原始指针的使用不会像在运行时那样变成直接内存访问,而是像查找分配地址的哈希图一样,

@glaebhoerl好吧,这几乎就是我描述的模型以及@tsionmiri正在实施的模型。

我认为 FFI 的区别非常重要,因为纯度是连贯性的_必需_。
你_couldn't_甚至为 Rust const fn s 使用 GHC,因为它有unsafePerformIO

我自己不太喜欢const关键字,这就是为什么我可以使用const fn foo<T: Trait>而不是const fn foo<T: const Trait> (因为需要const impl Trait for T )。

就像Sized一样,我们可能有错误的默认值,但我还没有看到任何其他可行的建议。

@eddyb我认为您的意思是链接到https://internals.rust-lang.org/t/mir-constant-evaluation/3143/31 (评论 31,而不是 11)。

@tsion 已修复,谢谢!

如果我完全偏离主题,请忽略这一点。

我在这个 RFC 中看到的问题是,作为用户,您必须标记尽可能多的函数const fn ,因为这可能是最佳实践。 目前在 C++ 中使用 conexpr 正在发生同样的事情。 我认为这只是不必要的冗长。

D 没有const fn但它允许在编译时调用任何函数(有一些例外)。

例如

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

请注意,我并不是真正的 Rust 用户,几分钟前我才阅读了 RFC,所以我可能误解了一些东西。

我没有看到任何最近的评论来解释这里的阻止程序,并且操作不是很有启发性。 什么状态。 我们如何才能将其移过终点线?

这是 Rocket 使用的: https ://github.com/SergioBenitez/Rocket/issues/19#issuecomment -269052006

请参阅https://github.com/rust-lang/rust/issues/29646#issuecomment -271759986。 此外,我们需要重新考虑我们对明确性的立场,因为 miri 将极限推到了“全局副作用”( @solson@nikomatsakis刚刚在 IRC 上谈论这个)。

我在这个 RFC 中看到的问题是,作为用户,您必须标记尽可能多的函数 const fn,因为这可能是最佳实践。

虽然我们可以使任意函数可调用,但如果这些函数访问 C 代码或静态数据,我们将无法计算它们。 作为一种解决方案,我建议使用 lint 警告可能是 const fn 的公共函数。

我同意棉绒。 它类似于现有的内置 lints missing_docsmissing_debug_implementationsmissing_copy_implementations

但是,默认情况下启用 lint 存在一些问题......它会警告您明确不想成为const的函数,例如,因为您计划稍后更改该函数不能是const并且不想将您的界面提交给const (删除const是一项重大更改)。

我猜#[allow(missing_const)] fn foo() {}在这些情况下可能有用吗?

@eddyb @nikomatsakis我的“删除const是一个重大变化”点表明我们毕竟希望拥有关键字,因为它向下游承诺fn将_remain_ const直到下一个主要版本。

将需要通过std和其他库洒多少const将是一种耻辱,但我不知道如何避免它,除非它只需要在 public-面对项目,这似乎是一个令人困惑的规则。

除非它只在面向公众的物品上需要,这似乎是一个令人困惑的规则。

我喜欢这个……我认为这不会令人困惑。 您的公共接口受到保护,因为您无法创建由const fn调用的函数非常量

从技术上讲,最好将函数注释为notconst ,因为我希望const fn比其他方式更多。

notconst也会更符合 Rust 的设计理念。 (即“ mut ,而不是const ”)

除非它只在面向公众的物品上需要,这似乎是一个令人困惑的规则。

我喜欢这个……我认为这不会令人困惑。

我在这个想法上摇摆不定。 它有它的好处(在做出公共接口决策时考虑const fn ),但我想到了另一种可能令人困惑的方式:

您的公共接口受到保护,因为您无法创建由const fn调用的函数非常量

这是真的,不幸的是,这意味着当库作者标记公共函数const时,他们也隐式标记了由该函数const传递调用的所有函数,并且有机会他们无意中标记了他们不想标记的函数,从而阻止他们将来使用非常量特性重写这些内部函数。


我希望const fn比其他方式更多。

我这样想了一段时间,但它只适用于纯 Rust 库 crates。 不可能将基于 FFI 的 fns 设为 const(即使它们只是基于 FFI 的传递,这是很多东西),因此const fn的绝对数量可能不会那么糟糕正如你我所想。


我目前的结论:任何非明确的const fn似乎都有问题。 可能没有避免大量编写关键字的好方法。

此外,郑重声明, notconst将是一个重大变化。

@solson一个很好的观点。

请记住,如果您尝试将关键字与 trait 方法一起使用,它会变得更加复杂。 将其限制在特征定义中是不够用的,并且注释 impl 会导致不完美的“const fn parametrim”规则。

我觉得当我们一开始采用const fn时,这种权衡已经得到了彻底的讨论。 我认为@solson的分析也是正确的。 我想唯一改变的是,也许警员 fns 的百分比变大了,但我认为这不足以改变这里的基本权衡。 逐渐不得不将const fn添加到您的公共界面等等会很烦人,但这就是生活。

@nikomatsakis困扰我的是这两个事实的结合:

  • 我们无法提前检查所有内容, unsafe代码可以是“动态非常量”
  • 无论我们为泛型和特征实现做什么,都将在“正确”和灵活之间进行权衡

鉴于“全局副作用”是阻止代码成为const fn的主要因素,这不是 Rust 曾经拥有并被删除的“效果系统”吗?
我们不应该谈论“效果稳定性”吗? 似乎类似于假设某些库永远不会使 IMO 恐慌的代码。

@eddyb绝对const是一个效果系统,是的,它确实有所有的缺点,让我们想尽可能地避免它们......如果我们要忍受添加的痛苦,这是合理的在效果系统中,我们可能需要考虑一些可以扩展到其他类型效果的语法。 例如,我们用unsafe (也是一种效果)支付了类似的价格,尽管我不确定考虑统一这些是否有意义。

违规可能动态发生的事实似乎更有理由选择加入,不是吗?

这个怎么样:

一般来说,我认为, const fn应该只用于构造函数( new )或绝对必要的地方。

但是,有时您可能希望使用其他方法来方便地创建常量。 我认为我们可以通过将 constness 设为默认值但仅适用于定义模块来解决许多情况下的问题。 这样,依赖项不能假设 const ,除非用const明确保证,同时仍然可以方便地使用函数创建常量,而无需将所有内容都设为 const

@torkleyy您已经可以通过使用未导出的助手来做到这一点。

我没有看到一个强有力的论点,即私有辅助函数不应该隐含const ,如果可能的话。 我认为@solson是说使const显式,即使对于辅助函数,也会迫使程序员暂停并考虑他们是否要提交该函数const 。 但是,如果程序员已经被要求为公共功能考虑这一点,那还不够吗? 不用到处写const不是值得吗?

在 IRC上,@eddyb提议拆分此功能门,以便我们可以在弄清楚它们的声明和正文的细节之前稳定对 const fns 的调用。 这听起来是个好主意吗?

@durka这对我来说听起来很棒,作为一个对编译器内部了解不多的 Rust 用户。

请原谅我在这里缺乏理解,但是在不稳定声明的情况下稳定对const函数的调用意味着什么。

我们是说编译器会通过某种方式以某种方式知道什么是常量,什么不是常量,但暂时将该部分留给讨论/实现?

如果编译器以后可能会改变对常量的看法,那么如何稳定调用呢?

@nixpulvis标准库中已经存在一些const fn ,例如UnsafeCell::new 。 该提议将允许在常量上下文中调用此类函数,例如static项的初始化程序。

@nixpulvis我的意思是从常量上下文调用由不稳定使用代码(例如标准库)定义的const fn函数,而不是在稳定的 Rust 代码中定义的常规函数​​。

虽然我都赞成首先稳定对const fn的调用,如果这可以更快地发生,我不清楚是什么阻碍了稳定所有const fn功能。 今天剩下的问题是什么? 解决它们的途径是什么?

@SimonSapin更多的是,我们不清楚今天声明const fn的设计是否可以很好地扩展,我们也不确定它们与特征之间的相互作用以及应该有多大的灵活性。

我想我倾向于稳定 const fn 的使用。 这似乎是符合人体工程学和表现力的胜利,我仍然无法想象比仅仅能够“编写普通代码”更好的方式来处理编译时常量评估。

稳定 const fn 的使用。

这也将标准库中的某些功能稳定为const ,库团队至少应该进行一些审核。

我已经提交了一个 PR https://github.com/rust-lang/rust/issues/43017来稳定调用,以及每个@petrochenkov 要审计的函数列表。

我有一个关于如何在某些 trait/impl 情况下使用它的问题/评论。 假设,假设我们有一个具有Zero特征的数学库:

pub trait Zero {
    fn zero () -> Self;
}

这个特性不需要zero方法是const ,因为这会阻止它被 $#$ Vec $#$ 支持的某些BigInt类型实现。 但是对于机器标量和其他简单类型,如果方法const会更实用。

impl Zero for i32 {
    const fn zero () -> i32 { 0 } // const
}

impl Zero for BigInt {
    fn zero () -> BigInt { ... } // not const
}

该特征不要求方法为const ,但仍应允许,因为const正在向实现添加限制而不是忽略限制。 这可以防止某些类型具有相同功能的普通版本和const版本。 我想知道这个问题已经解决了吗?

为什么你希望 trait 的不同实现表现不同? 您不能在通用上下文中使用它。 您可以使用 const fn 在标量上创建本地 impl。

@Daggerbot这是我在特征中看到const fn的唯一方法 - 具有特征要求所有 impls 都是const fn远不如有效地具有“ const impl s”常见.

@jethrogb你可以,虽然它要求常量是impl的一个属性。
我想到的是一个通用的const fn ,例如T: Zero绑定,对于 $#$7$# 将需要impl Zero $5$# T s 调用它只包含const fn方法,调用来自一个常量上下文本身时(例如另一个const fn )。

它并不完美,但没有提出更好的替代方案 - IMO 最接近的选择是“如果在编译时尝试任何不可能的事情,则允许调用堆栈深处的任何调用和错误”,这并不像它那么糟糕可能看起来第一印象 - 对它的大部分关注与向后兼容性有关,即标记函数const fn确保记录事实并且执行在编译时无效的操作将需要使其不const fn

这不能解决问题吗?

pub trait Zero {
    fn zero() -> Self;
}

pub trait ConstZero: Zero {
    const fn zero() -> Self;
}

impl<T: ConstZero> Zero for T {
    fn zero() -> Self {
        <Self as ConstZero>::zero()
    }
}

可以使用宏来减少样板文件。

除了具有几乎完全相同的两个独立特征( ZeroConstZero )的小不便之外,我在使用一揽子实现时发现了一个潜在问题:

// Blanket impl
impl<T: ConstZero> Zero for T {
    fn zero () -> Self { T::const_zero() }
}

pub struct Vector2<T> {
    pub x: T,
    pub y: T,
}

impl<T: ConstZero> ConstZero for Vector2<T> {
    const fn const_zero () -> Vector2<T> {
        Vector2 { x: T::const_zero(), y: T::const_zero() }
    }
}

// Error: This now conflicts with the blanket impl above because Vector2<T> implements ConstZero and therefore Zero.
impl<T: Zero> Zero for Vector2<T> {
    fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

如果我们删除一揽子 impl,错误就会消失。 总而言之,这可能是在编译器中最容易实现的,因为它为语言增加了最少的复杂性。

但是如果我们可以将const添加到不需要的已实现方法中,我们可以避免这种重复,尽管仍然不完美:

impl<T: Zero> Zero for Vector2<T> {
    const fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

IIRC,C++ 在使用constexpr时允许这样做。 这里的缺点是这个const只有在<T as Zero>::zero也是const时才适用。 这应该是一个错误,还是编译器在它不适用时忽略这个const (如 C++)?

这些例子都没有完美地解决这个问题,但我真的想不出更好的方法。

编辑: @andersk的建议将使第一个示例成为可能而不会出现错误。 就编译器实现而言,这可能是最好/最简单的解决方案。

@Daggerbot这听起来像是在RFC 1210 (specialization)末尾提出的“格”规则的用例。 如果你写

impl<T: ConstZero> Zero for T {…}  // 1
impl<T: ConstZero> ConstZero for Vector2<T> {…}  // 2
impl<T: Zero> Zero for Vector2<T> {…}  // 3
impl<T: ConstZero> Zero for Vector2<T> {…}  // 4

那么虽然 1 与 3 重叠,但它们的交点正好被 4 覆盖,因此在格规则下是允许的。

另请参阅http://smallcultfollowing.com/babysteps/blog/2016/09/24/intersection-impls/。

这是一个非常复杂的系统,我们想要避免。

是的,需要格规则。

@eddyb你认为什么复杂?

@Kixunil复制标准库中的几乎每一个特征,而不是“简单地”将一些impl标记为const fn

我们在这里偏离了轨道。 目前的问题是关于稳定const fn的使用。 允许const fn trait 方法或const impl Trait for Foo彼此正交并且与接受的 RFC 正交。

@oli-obk 这不是新的 RFC,而是const fn的跟踪问题。

我刚刚注意到,并编辑了我的评论。

@eddyb是的,但是对于编译器来说它更简单(减去专业化,但我们可能还是想要专业化)并且允许人们也受到ConstTrait的约束。

无论如何,我不反对将 impls 标记为 const。 我也在想象编译器自动生成ConstTrait: Trait

@Kixunil这并不简单,尤其是如果您可以通过专业化来做到这一点。
编译器不必自动生成像ConstTrait: Trait这样的任何东西,特征系统也不需要知道这些,只需要递归实现(具体的implwhere bound) 特征系统提供并检查它们。

我想知道 const fns 是否应该禁止访问UnsafeCell 。 可能有必要允许真正的 const 行为:

const fn dont_change_anything(&self) -> bool {
    let old = self.cell.get();
    self.cell.set(!old);
    old
}

到目前为止,我已经看到set不是const 。 问题是这是否会永远存在。 换句话说: unsafe代码能否依赖于在不可变数据上运行相同的const函数时,在今天以及在语言/库的每个未来版本中总是返回相同的结果?

const fn并不意味着不可变,它意味着可以在编译时调用。

我知道了。 如果我能以某种方式保证一个函数在不使用unsafe特征的情况下多次调用时总是返回相同的东西,我将不胜感激,如果有可能的话。

@jethrogb感谢您的链接!

我注意到mem::size_of每晚都以const fn的形式实现。 这对mem::transmute和其他人来说可能吗? Rust 内在函数在编译器内部运行,我还不够熟悉,无法进行适当的更改以允许这样做。 否则,我很乐意实施它。

不幸的是,对值进行操作比神奇地创建一些值要困难一些。 transmute需要 miri。 迈向编译器的第一步已经在进行中:#43628

所以! 对 const 稳定*Cell::newmem::{size,align}_ofptr::null{,_mut}Atomic*::newOnce::new{integer}::{min,max}_value有兴趣吗? 我们应该在这里设置 FCP 还是创建单独的跟踪问题?

是的。

我不是任何对此有决定权的团队的一员,但我个人的看法是,除了mem::{size,align}_of之外的所有这些都微不足道,以至于现在可以稳定它们而无需通过橡皮图章的动作FCP。

作为用户,我想尽快在 const 表达式中使用mem::{size,align}_of ,但我读过@nikomatsakis表达了对它们在被制作const fn时是 insta-const-stable 的担忧. 我不知道是否有具体问题或只是一般警告,但 IIRC 这就是添加每个功能特性门的原因。 我想这两者的担忧会足够相似,以至于他们可以共享一个 FCP。 我不知道@rustbot是否可以在同一个 GitHub 线程中处理单独的 FCP,所以最好打开单独的问题。

@durka你能打开一个跟踪问题来稳定所有这些函数的常量吗? 一旦完成,我会建议 FCP。

跟随关于 alloc::Layout 上的 const fns 的讨论
是否可以在const fn中允许恐慌并将其视为编译错误? 这类似于现在用常量算术表达式所做的,不是吗?

是的,一旦 miri 被合并,这是一个非常微不足道的功能

这是请求其他std函数变为const的正确位置吗? 如果是这样, Duration:: { newfrom_secsfrom_millis } 应该都可以安全地制作const

@remexre实现它的最简单方法可能是进行 PR 并要求那里的 libs 团队审查。

公关为https://github.com/rust-lang/rust/pull/47300。 我还在不稳定的构造函数中添加了const

关于允许进一步声明std函数的任何想法const ? 具体来说, mem::uninitializedmem::zeroed ? 我相信这两个都是附加const功能的合适人选。 我能想到的唯一缺点是mem::uninitialized的相同缺点,其中实现Drop的结构的创建是在没有ptr::write的情况下创建和覆盖的。

如果这听起来合适,我也可以附上 PR。

这样做的动机是什么? 允许制作无法覆盖的无效位模式(因为它们在 const 中)似乎是一个无用的脚枪,但也许我忽略了显而易见的事情。

mem::uninitialized绝对是一把脚枪,如果瞄准不正确,也会从你的手中射出。 说真的,我不能夸大使用这个函数的危险性,尽管它被标记为unsafe

声明这些附加函数const背后的动机源于这些函数的性质,因为调用mem::uninitialized<Vec<u32>>每次都会返回相同的结果,没有副作用。 显然,如果不进行初始化,这是一件很糟糕的事情。 因此, unsafe仍然存在。

但是对于一个用例,考虑一个全局计时器,它跟踪某个功能的开始。 它的内部状态将在稍后确定,但我们需要一种方法将其呈现为在执行时创建的静态全局结构。

use std::time::Instant;

pub struct GlobalTimer {
    time: UnsafeCell<Instant>
}

impl TimeManager {
    pub const fn init() -> TimeManager {
        TimeManager {
            time: UnsafeCell::new(Instant::now())
        }
    }
}

此代码无法编译,因为Instant::now()不是const函数。 如果mem::uninitializedconst fn ,则将Instant::now()替换为mem::uninitialized::<Instant>())将解决此问题。 理想情况下,一旦程序开始执行,开发人员将初始化这个结构。 虽然这段代码被认为是非惯用的 rust(全局状态通常非常糟糕),但这只是全局静态结构有用的众多情况之一。

我认为这篇文章为 Rust 代码在编译时运行的未来奠定了良好的基础。 全局、编译时静态结构是一个具有一些重要用例(也想到操作系统)的特性,目前缺少 rust。 通过慢慢地将const添加到被认为合适的库函数中,例如mem::uninitializedmem::zeroed ,尽管它们有unsafe标记,但可以朝着这个目标迈出一小步。

编辑:忘记了const函数签名中的TimeManager::init()

嗯,该代码确实可以编译,所以我仍然错过了这里的确切动机......如果你可以编写代码,例如

const fn foo() -> Whatever {
    unsafe { 
        let mut it = mem::uninitialized();
        init_whatever(&mut it);
        it
    }
}

但是 const fns 目前非常受限制,你甚至不能写...

我很欣赏理论上的理由,但constpure不同,如果某些引人注目的用例没有必要,我认为我们不应该做任何事情来鼓励使用这些功能.

我认为有很多较低的悬而未决的果实可以首先稳定下来。 如果没有 miri,那么未初始化和归零的内在函数无论如何都没有意义。 不过,我希望有一天能见到他们。 我们甚至可以最初稳定它们并要求所有常量必须产生一个初始化的结果,即使中间计算可以未初始化。

也就是说,使用联合和不安全的代码,您无论如何都可以模拟未初始化或归零,因此保持它们非常量没有多大意义

union的帮助下,前面的代码现在可以编译. 这绝对是可怕的😅。

所有的优点也是如此。 这些内在函数在用例列表中的位置非常低,但它们仍然是最终const的合适候选者。

这太可怕了。

那么...为什么您究竟主张将 mem::uninitialized 设置为
反对,比如说, Instant::now? :)

需要为具有非常量的结构提供常量初始化器
内饰是真实的(请参阅:互斥锁)。 但我不认为做这个坏事
更容易是获得它的正确方法!

2018 年 1 月 25 日星期四凌晨 2:21,斯蒂芬·弗莱施曼 <
通知@github.com> 写道:

在联合的帮助下,前面的代码现在可以编译了
https://play.rust-lang.org/?gist=be075cf12f63dee3b2e2b65a12a3c854&version=nightly
这绝对是可怕的😅。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/rust-lang/rust/issues/24111#issuecomment-360382201
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAC3n-HyWD6MUEbfHkUUXonh9ORGPSRoks5tOCtegaJpZM4D66IA
.

Instant::now不能是常量。 该函数会返回什么? 编译时间?

有人可以总结一下稳定这一点需要做些什么吗? 需要做出什么决定? 是否要稳定这一点?

与模式集成(例如 https://gist.github.com/d0ff1de8b6fc15ef1bb6)

我已经评论了要点,但鉴于const fn目前无法在模式中匹配,这不应该阻止稳定,对吧? 如果有意义的话,我们总是可以在事后允许它。

Instant::now 不能是 const。 该函数会返回什么? 编译时间?

但可能有Instant::zero()Instant::min_value()是 const。

有人可以总结一下稳定这一点需要做些什么吗? 需要做出什么决定? 是否要稳定这一点?

我认为唯一的悬而未决的问题是我们的 const fn 检查是否足够严格,不会意外地允许/稳定我们在 const fn 中不想要的东西。

我们可以通过 rust-lang/rfcs#2272 与模式集成吗? 模式已经像现在一样痛苦,我们不要让它们变得更痛苦。

我认为唯一的悬而未决的问题是我们的 const fn 检查是否足够严格,不会意外地允许/稳定我们在 const fn 中不想要的东西。

如果我错了,请纠正我,但这些检查是否与const右值正文中当前允许的检查相同? 我的印象是const FOO: Type = { body };const FOO: Type = foo(); const fn foo() -> Type { body }在它们允许任意实体的情况下是相同的

@sgrif我认为关注点在于参数, const fn有但const没有。
此外,从长远来看,我们还不清楚我们是否希望保留今天的const fn系统。

您是否建议使用 const 泛型(两种方式)? (例如<const T> + const C<T>又名const C<const T> ?)

我真的很想有一个try_const!宏,它会尝试在编译时评估任何表达式,如果不可能的话会恐慌。 这个宏将能够调用非常量 fns(使用 miri?),所以我们不必等到 std 中的每个函数都被标记为 const fn。 但是顾名思义,它随时可能失败,所以如果一个函数被更新并且现在不能是 const,它将停止编译。

@Badel2我理解你为什么想要这样的功能,但我怀疑它的广泛使用对 crates 生态系统来说真的很糟糕。 因为这样一来,您的 crate 可能最终取决于另一个 crate 中可编译时可评估的函数,然后 crate 作者更改了一些不影响函数签名但阻止函数在编译时可评估的内容。

如果函数首先被标记const fn ,那么 crate 的作者在尝试编译 crate 时会直接发现问题,您可以依赖注释。

如果只有这在操场上有效...... https://play.rust-lang.org/?gist=6c0a46ee8299e36202f959908e8189e6&version=stable

这是一种将构建时间包含在构建程序中的不可移植(实际上,它是如此不可移植,以至于它可以在我的系统上运行,但不能在操场上运行 - 但它们都是 linux)。

可移植的方法是允许 SystemTime::now() 在 const 评估中。

(这是任何函数/表达式的 const/compile-time-eval 的参数,无论它是否const fn 。)

在我看来,这听起来像是在 include_bytes 中禁止绝对路径的论据😃

如果您在 const fn 中允许 SystemTime::now, const FOO: [u8; SystemTime::now()] = [42; SystemTime::now()];将根据您的系统性能、调度程序和 Jupiter 的位置随机出错。

更糟糕:

const TIME: SystemTime = SystemTime::now();

并不意味着 TIME 的值在所有使用站点都相同,尤其是在增量编译和 crate 之间。

更疯狂的是,您可以以非常不合理的方式搞砸foo.clone() ,因为您最终可能会从长度为 3 的数组中选择克隆 impl,但返回类型可能是长度为 4 的数组。

因此,即使我们允许调用任意函数,我们也永远不会允许 SystemTime::new() 成功返回,就像我们永远不会允许真正的随机数生成器一样

@SoniEx2我想这有点离题了,但是您今天已经可以使用 cargo build.rs文件来实现类似的东西。 请参阅 Cargo Book 中的Build Scripts ,特别是有关代码生成案例研究的部分。

@oli-obk 我认为这不是完全相同的问题,因为一个与版本控制 API 安全有关,而另一个与构建环境有关,但是我同意如果不小心应用它们都可能导致生态系统破坏。

不允许const fn中获取当前时间; 我们不需要添加更多/更简单的方法来使构建不可重现。

我们不能允许任何类型的非确定性(如随机数、当前时间等)进入const fn - 允许这会导致类型系统不健全,因为 rustc 假定常量表达式总是计算给定的相同结果相同的输入。 有关更多说明,请参见此处

处理https://github.com/rust-lang/rust/issues/24111#issuecomment -376352844 等案例的未来方法是使用一个简单的过程宏,它获取当前时间并将其作为纯数字或字符串令牌。 过程宏或多或少是完全不受限制的代码,可以通过非 const Rust 代码使用的任何常用可移植方式来获取时间。

@rfcbot fcp 合并

我建议我们合并它,因为它是一个有点理智的选项,不是重大更改,防止意外的重大更改(以使其不可评估的方式更改函数,而其他 crate 在 const 上下文中使用该函数)并且唯一的真正糟糕的是,我们必须在一堆函数声明之前抛出const

@rfcbot fcp 代表 @oli-obk 合并 - 似乎值得考虑稳定和讨论问题

团队成员@nrc已提议将其合并。 下一步是由其他标记的团队进行审查:

  • [x] @aturon
  • [x] @cramertj
  • []@eddyb
  • [x] @joshtriplett
  • []@nikomatsakis
  • [x] @nrc
  • []@pnkfelix
  • [x] @scottmcm
  • [x] @withoutboats

关注点:

  • 设计(https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588)
  • https://github.com/rust-lang/rust/issues/24111#issuecomment -377133537 解决的并行常量特征
  • 优先级(https://github.com/rust-lang/rust/issues/24111#issuecomment-376652507)
  • 运行时指针地址(https://github.com/rust-lang/rust/issues/24111#issuecomment-386745312)

一旦大多数审阅者批准(并且没有人反对),这将进入其最终评论期。 如果您发现在此过程中任何时候都没有提出的重大问题,请说出来!

有关标记团队成员可以给我的命令的信息,请参阅此文档

@rfcbot关注优先级

我们可能想在版本结束之前对此进行讨论,因为我认为我们没有足够的带宽来处理任何后果。

@rfcbot关注一切-常量

我们最终进入了一个 C++ 世界,在这个世界中,有动力让每个函数都可以const

与@oli-obk 的简短讨论摘要:

  1. 将来,几乎每个函数都可以标记const 。 例如, Vec上的所有内容都可能是const 。 在那个世界中,完全摆脱const关键字可能是有意义的:几乎所有东西都可以是const ,并且必须不遗余力地将函数从 const 更改为 non -const,因此关于推断的 const 的向后兼容性风险可能不会非常高。

  2. 但是,今天摆脱const是不可行的。 今天的 miri 不能解释一切,而且在生产中也没有经过彻底的测试。

  3. 今天需要const实际上是向后兼容的,然后弃用这个关键字并在将来使用推断的 constness。

将 1、2 和 3 放在一起,今天稳定const关键字似乎是一个不错的选择,而不是在未来版本中扩展常量可评估函数集。 一段时间后,我们将拥有一个久经考验的hony badger常数评估器,它可以评估一切。 此时,我们可以切换到推断的 const。

Wrt 后果危险: const fn 已在夜间广泛使用,尤其是在嵌入式上。 此外,const fn 检查器与用于静态初始化器和常量的检查器相同(除了一些静态特定检查和函数参数)。

我看到的主要缺点是我们基本上提倡在板条箱上自由喷洒const (现在,请参阅@matklad的帖子了解未来的想法

@rfcbot关注并行常量特征

感觉稳定它会立即导致一堆板条箱制作一个平行的特征层次结构,前面有ConstConstDefaultConstFromConstIntoConstCloneConstTryFromConstTryInto等并要求ConstIndex等。 这并不可怕——我们今天肯定有Try的情况,虽然稳定 TryFrom 会有所帮助——但我觉得至少有一个更好地解决它的计划草图会很好。 (那是 https://github.com/rust-lang/rfcs/pull/2237 吗?我不知道)。

@nrc :看起来机器人只记录了你的一个问题)

Parallel-const-traits 在假设的未来 const-all-the-things 版本中具有简单的解决方案。 他们只会工作。

在 const fn 世界中,只要我们不允许 const fn 特征方法(我们不允许),就不会因为您不能而导致特征重复。 您当然可以创建关联的常量(每晚),这是 libstd 一年前的情况,我们有一堆常量用于初始化静态/常量中的各种类型,而不会暴露它们的私有字段。 但这可能已经发生了一段时间,但没有发生。

需要明确的是,今天ConstDefault已经可以在没有const fn的情况下使用,其余的例子( ConstFromConstIntoConstCloneConstTryFrom , ConstTryInto ) 即使这个特性稳定了也不可能,因为它没有像@oli-obk 提到的那样添加 const trait 方法。

ConstDefault可以通过使用关联的 const 而不是关联的 const fn,但据我所知,它的功能是等效的。)

@scottmcm const fn今天不可能在特征定义中使用(哦@solson已经提到过)。

@eddyb随机想法:如果我们可以使用const impl一个特征而不是在特征定义中添加const fn会怎样? (这两者也不是相互排斥的。)

@whitequark https://github.com/rust-lang/rfcs/pull/2237涵盖了这个想法,通过在每个fn $ 上将const impl扩展为const fn $ impl ,并允许带有所有const方法的impl #$ 满足T: const Trait绑定,而无需在const中标记任何方法特征定义本身。

@rfcbot关注设计

历史上,出于以下几个原因,我们一直致力于稳定任何特定的const fn系统:

  • 当前的不支持需要const fn方法的 $#$1 trait #$ s,或提供const fn方法的 trait impl s(参见 https://github. com/rust-lang/rfcs/pull/2237 了解一些方法)
  • 相关地,有一个问题是通过T: Trait来请求使用的方法,该方法绑定为const fn而没有单独的特征,最好在编译时使用(例如Option::map将在运行时工作相同,但需要在 CTFE 中使用 const 可调用闭包)
  • 由于许多算法、集合和抽象都可能是 const 可评估的,因此可能会有整个 crate 将在任何地方使用const fn (想到libcore

有不同的设计选择可以缓解大部分或所有这些问题(以引入其他问题为代价),例如,这些是已经出现的几个:

  • 不需要任何注释,只发出编译器错误,miri 无法评估

    • 优点:更干净的代码库,持续评估可能已经失败,具体取决于所涉及的值

    • 缺点:没有语言级别的行为文档和 semver 边界,您可以将任何其他人的代码扔到 miri 并观察他们所做的任何微小更改,例如在您的依赖项之一的补丁版本中添加运行时调试日志; 此外,右值提升更难

  • 一种根据功能/实现/模块/等选择上述行为的方法。

    • 这些函数的主体(至少在const fn方面)表现得像宏

    • 优点:没有 semver 影响,限制了一些分析的范围

    • 缺点:仍然不是很好的文档,不清楚应该影响什么行为(只是const-evaluatability?),身体的任何变化都可以算作 semver-break

因为这样一来,您的 crate 可能最终取决于另一个 crate 中可编译时可评估的函数,然后 crate 作者更改了一些不影响函数签名但阻止函数在编译时可评估的内容。

@leoschwarz这不是自动特征的问题吗? 也许解决这个问题的方法是将 rust-semverver 与货物集成以检测这种意外破损。

也就是说,我不清楚如果 miri 有一个您(作为库作者)意外超过的评估时间限制,会导致下游编译失败。

@nrc我认为“everything-const”是真的,但不是问题。 是的,我们最终会标记大量的东西const

只是想指出我不确定我是否希望推断出的所有内容都是
常量。 这是关于运行时间或编译时间是否更多的决定
重要的。 有时我认为编译器在
编译时间!

2018 年 3 月 28 日,星期三,下午 2:49,Josh Triplett [email protected]
写道:

@nrc https://github.com/nrc我认为“everything-const”是真的,但不是
一个问题。 是的,我们最终会标记大量的东西 const。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/rust-lang/rust/issues/24111#issuecomment-376914220
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAC3ny9Wm9JK6p-fXf6gbaEgjFtBpMctks5ti6LigaJpZM4D66IA
.

评估时限

这个限制很快就没有了。

只是想指出,我不确定我是否希望所有推断为 const。

哦不,我们不会在编译时随机计算。 只允许在静态、常量、枚举变量判别式和数组长度的主体中计算随机事物

@rfcbot解决了并行常量特征

谢谢各位指正!

这个限制很快就没有了。

惊人的。 在这种情况下,auto-const-fn(结合一些 rust-semverver 或类似的集成以提供有关损坏的信息)听起来很棒,尽管“添加日志记录并导致损坏”可能是有问题的。 虽然我猜你可以增加版本号,但它们并不是有限的。

在我的常量模型中,记录和打印是“很好”的副作用。 如果每个人都同意,我们将能够找到解决方案。 我们甚至可以写入文件(不是真的,但表现得好像我们做了并丢弃了所有东西)。

我真的很担心默默地丢弃副作用。

一旦我们围绕它们创建了一个 RFC,我们就可以讨论这个问题。 现在你不能在常量中有“副作用”。 该主题与稳定 const fn 正交

我有点担心“只是做一个 semver 警告”的方法来推断
恒常性。 如果一个从未考虑过 constness 的 crate 作者看到
“警告:您刚刚所做的更改使得无法调用 foo()
const context,这在以前是可能的”,他们会不会只是将其视为
不合逻辑而沉默吗? 显然,这个问题上的人经常认为
关于哪些函数可以是 const。 如果更多人这样做就好了
那个(一旦 const_fn 稳定)。 但是,出乎意料的警告是正确的吗?
鼓励的方式?

2018 年 3 月 29 日星期四凌晨 4:36,Oliver Schneider [email protected]
写道:

一旦我们围绕它们创建了一个 RFC,我们就可以讨论这个问题。 现在你只是
常量中不能有“副作用”。 该主题与
稳定常数 fn


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/rust-lang/rust/issues/24111#issuecomment-377164275 ,
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAC3n3MtmvrDF42Iy0nhZ2q8xC-QGcvXks5tjJ0ggaJpZM4D66IA
.

我认为显式 const fn 可能会很烦人并且会使许多 API 变得混乱,但是我认为隐式假设的替代方案有太多的问题不可行:

  • 如果将其称为 const 或 non const,则丢弃副作用可能会导致代码行为不同(例如写入然后读取文件)。
  • 调用 extern 函数意味着总会有副作用,因此可能永远不会推断出不安全的代码 const fn。
  • 什么时候可以为通用代码推断出 const fn? 这会在单态化时完成吗?

然而,我确实看到了最大的问题是没有明确表示有人可以通过一次更改意外破坏大量代码,甚至没有意识到这一点。 这尤其与 Rust 生态系统中常见的长依赖图有关。 如果它需要对函数签名进行显式更改,人们会更容易意识到这是一项重大更改。

也许这样的功能可以实现为 crate 级别的配置标志,可以添加到 crate 的根目录, #![infer_const_fn]或类似的东西,并且永远保持选择加入。 如果添加了标志 const fn 将在可能的情况下在 crate 中推断出来并反映在文档中(并且它要求调用的函数也是 const fn ),如果 crate 作者添加了这个标志,他们承诺要谨慎关于版本控制,甚至可能会强制使用 rust-semverver。

倒着做怎么办?

与其拥有 const fn,不如拥有 side fn。

它仍然是显式的(您需要将侧 fn 调用侧 fn,显式破坏兼容性),并消除混乱。 (一些)内在函数和任何带有 asm 的东西都是 fn 的一个侧面。

这不是向后兼容的,虽然我猜它可以添加到一个版本中?

格林威治标准时间 2018 年 3 月 30 日凌晨 2:43:06+08:00,“Soni L.” [email protected]写道:

倒着做怎么办?

与其拥有 const fn,不如拥有 side fn。

它仍然是明确的(你需要把边 fn 来调用边 fn,
明确破坏兼容性),并消除混乱。 (一些)
内在函数和任何带有 asm 的东西都是 fn 的一个侧面。

--
你收到这个是因为你被提到了。
直接回复此邮件或在 GitHub 上查看:
https://github.com/rust-lang/rust/issues/24111#issuecomment -377333542

--
使用 K-9 Mail 从我的 Android 设备发送。 请原谅我的简短。

我认为更大的问题是这对初学者来说是一个真正的冲击,因为这不是大多数编程语言所做的。

@whitequark我不同意这样做的任何事情(“抛弃副作用”),我认为@oli-obk 正在谈论未来的扩展,但从我参与的讨论中,我知道以下内容

  • 我们可以区分“确定性副作用”(不会给你返回不纯的数据)
  • 如果我们想“在编译时记录数据”,我们可以有特殊的 API

    • 这取决于您想要什么级别的外部确定性,这非常困难,因为增量按需编译不一定会产生一致的输出

  • 具体来说,我们不会触及任何当前存在并使用 globals / C FFI

编辑:这样讨论就不会脱轨,例如:

如果将其称为 const 或 non const,则丢弃副作用可能会导致代码行为不同(例如写入然后读取文件)。

我们可以(可能?)都假设@oli-obk 关于丢弃这样的副作用是错误的。

也许这样的特性可以作为一个 crate 级别的配置标志来实现,可以添加到 crate 的根目录中

这是过去建议的第二个示例的子集,来自https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588。
如果我们有一个作用域的“配置标志”,用户应该能够选择更细粒度的 IMO 作用域。

倒着做怎么办?
与其拥有 const fn,不如拥有 side fn。
它仍然是显式的(您需要将侧 fn 调用侧 fn,显式破坏兼容性),并消除混乱。 (一些)内在函数和任何带有 asm 的东西都是 fn 的一个侧面。

https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588 我试图指出整个库可能是“所有const fn ”或“所有side fn ”。
如果它不在函数声明中,而是在范围内,它可能会在未来的版本中工作。
但是,如果没有“从身体推断”语义,即使选择加入side fn ,您也必须设计特征交互,因此您不会获得任何东西,并且会引入潜在的巨大摩擦。

Kenton Varda 的“被认为有害的单身人士”文章的第 3.3 节似乎与此相关(老实说,整件事都值得一读)。

调试日志呢?

在实践中,每个人都承认调试日志记录应该可用于每段代码。 我们为此破例。 对于那些关心的人来说,这个例外的确切理论基础可以通过几种方式提供。

从安全的角度来看,调试日志记录是一个良性的单例。 它不能用作通信通道,因为它是只写的。 而且通过写入调试日志显然不可能造成任何损害,因为调试日志不是程序正确性的一个因素。 即使恶意模块向日志“发送垃圾邮件”,来自该模块的消息也很容易被过滤掉,因为调试日志通常可以准确地识别出每条消息是由哪个模块产生的(有时它们甚至会提供堆栈跟踪)。 因此,提供它没有问题。

可以提出类似的论点来表明调试日志不会损害可读性、可测试性或可维护性。

调试日志的另一个理论理由是调试日志功能实际上只是调试器恰好观察到的无操作。 当没有调试器正在运行时,该函数什么也不做。 一般来说,调试显然破坏了整个对象能力模型,但它也显然是一种特权操作。

我关于“​​我们可以找到[用于调试] 的解决方案”的声明确实是指一个潜在的未来 API,它可以从 consts 调用,但有某种形式的打印。 随机实现特定于平台的打印操作(这样我们就可以使带有打印/调试语句的现有代码成为 const)不是 const 评估器应该做的事情。 这将纯粹是选择加入,明确地没有不同的可观察行为(例如,const eval 中的警告和运行时的命令行/文件输出)。 确切的语义留给未来的 RFC,通常应该被认为与 const fn 完全正交

const impl TraitT: const Trait有什么明显的缺点吗?

除了更多地喷洒const之外,只有一些 trait 方法可能是 const,这需要更细粒度的控制。 我不知道一个简洁的语法来指定它。 也许where <T as Trait>::some_method is constis可能是上下文关键字)。

同样,这与 const fn 正交。

[u8; SizeOf<T>::Output]

如果 const 和 side fns 是分开的,我们必须考虑到实际的设计考虑。 将它们分开的最简单方法是将 const fns 扩展为我们今天拥有的东西 - 图灵完备的类型系统。

或者,使 const fns 真正成为 const:任何 const fn 都应该像每个参数都是 const 泛型一样进行评估。

这使它们更容易推理,因为我个人无法按照它们目前的立场对 const fns 进行推理。 我可以推理图灵完备的类型、宏、普通 fns 等,但我发现无法推理 const fn,因为即使是很小的细节也会完全改变它们的含义。

因为即使是很小的细节也会完全改变它们的含义。

你能详细说明一下吗? 你的意思是像const fn指针, const trait bounds 之类的扩展,......? 因为我在裸露的const fn提案中看不到任何小细节。

或者,使 const fns 真正成为 const:任何 const fn 都应该像每个参数都是 const 泛型一样进行评估。

这就是我们在编译时所做的。 只是在运行时该函数像任何其他函数一样使用。

问题是任何小细节都可以将 const eval 变成运行时 eval。 起初,这似乎不是什么大不了的事,但它可以

假设函数调用真的很长,因为它都是 const fns? 你想把它分成多行。

因此,您添加了一些let

现在您的程序运行时间要长 20 倍。

@SoniEx2数组的大小( $N [u8; $N] )总是在编译时评估。 如果该表达式不是const ,则编译将失败。 相反, let x = foo()将在运行时调用foo ,无论它是否是const fn (以优化器的内联和常量传播为模,但这与const fn完全不同)。 如果你想命名在编译时计算某个表达式的结果,你需要一个const项。

现在您的程序运行时间要长 20 倍。

这根本不是 const fn 的工作原理!

如果您声明一个函数const fn并在其中添加let绑定,您的代码将停止编译。

如果您从 const fn 中删除const const fn ,这是一个重大更改,将破坏该函数在内部的所有使用,例如conststatic或数组长度。 您的代码是运行时代码并运行const fn ,永远不会在编译时运行。 这只是一个正常的运行时函数调用,所以它不会变慢。

编辑: @SimonSapin打败了我:D

const fn 尽可能在编译时进行评估。

那是,

const fn random() -> i32 {
    4
}

fn thing() -> i32 {
    let i = random(); // the RHS of this binding is evaluated at compile-time, there is no call to random at runtime.
}

现在假设您有一个带参数的 const fn。 这将在编译时进行评估:

fn thing() {
    let x = const_fn_with_1_arg(const_fn_returns_value());
}

这将导致在运行时评估const_fn_with_1_arg

fn thing() {
    let x = const_fn_returns_value();
    let y = const_fn_with_1_arg(x); // suddenly your program takes 20x longer to run, and compiles 20x faster.
}

@eddyb我想知道是否可以通过观察“最小的const fn”与所有潜在的未来扩展前向兼容来解决设计问题? 也就是说,我的理解是我们要稳定

将自由函数和固有方法标记为 const,使它们能够在带有常量参数的常量上下文中调用。

这似乎与任何“特征的常量效应”设计完全兼容。 它也与“推断的 const”设计兼容,因为我们可以稍后使const成为可选的。

是否有任何替代的未来设计与当前的“最小 const fn”提案不兼容?

@nrc

请注意,rfcbot 没有注册您的everything-const关注点(每个评论一个关注点!)但是,它似乎是设计关注点的一个子集,我之前的评论(TL;DR: current minimum proposal完全兼容一切,我们可以在未来使const关键字可选)。

至于优先级/后果问题,我想记录我们已经讨论过的所有内容,以及我们尚未记录的内容:

  • const fn foo(x: i32) -> i32 { body }const FOO: i32 = body;相对较小的添加,因此影响的风险很小。 也就是说,大多数实际实现const fn的代码已经在稳定的编译器中工作了(免责声明:这是我从@oli-obk 那里听到的,我可能听错了)。

  • 嵌入式工作组非常想要 const fn :)

此外,请注意,不稳定const fn会导致库中次优 API 的扩散,因为它们必须使用ATOMIC_USIZE_INIT之类的技巧来解决缺少 const fns 的问题。

让我 = 随机(); // 此绑定的 RHS 在编译时评估,在运行时没有调用 random。

不,这根本没有发生。 它可能正在发生(并且 llvm 可能正在这样做),但是您不能期望任何依赖于启发式的编译器优化实际上会发生。 如果你想在编译时计算一些东西,把它放在const中,你就会得到保证。

所以 const fn 只能在 const 中进行评估,否则这基本上没用?

那么为什么不将 const 和非 const fn 严格分开呢?

看到语义是一团糟,因为它们故意混淆了编译时和运行时的东西。

所以 const fn 只能在 const 中进行评估,否则这基本上没用?

它们不是无用的,它们在运行时执行,就像其他任何函数一样。 这意味着您不必根据您是否在 const 评估中使用不同的 Rust“子语言”。

那么为什么不将 const 和非 const fn 严格分开呢?

const fn的全部动机是没有这种分离。 否则我们需要复制所有类型的函数: AtomicUsize::new() + AtomicUsize::const_new() ,即使两个主体是相同的。

你真的想写 90% 的libcore两次,一次用于 const eval,一次用于运行时? 许多其他箱子可能也是如此。

我在想AtomicUsize::Of<value> 。 是的,我宁愿把所有东西都写两次,也不愿有未知的保证。 (此外,这不会根据某些东西是否被 const eval'd 而表现不同。)

您可以在 const fn 中声明 const 以保证 const 评估(用于递归 const fn)吗? 还是您需要通过 const 泛型? 等等。

@SoniEx2作为示例,说明应如何编写示例以利用const fn并在任一函数变为非const时变成编译时错误:

fn thing() {
    const x: u32 = const_fn_returns_value();
    const y: u32 = const_fn_with_1_arg(x);
}

(操场上的完整运行示例)

由于没有类型推断,因此人体工程学稍差,但谁知道呢,将来可能会改变。

而不是有未知的保证。

你会这么好心,并举一些你认为有些地方不清楚的例子吗?

您可以在 const fn 中声明 const 以保证 const 评估(用于递归 const fn)吗? 还是您需要通过 const 泛型? 等等。

const fn的重点不是在编译时神奇地评估事物。 它能够在编译时评估事物。

自从 rustc 基于 llvm 以来,已经在编译时神奇地评估事物。 所以......正是它停止在 ocaml 中实现的时候。 我认为没有人想从 rustc 中删除持续传播。

const fn不会以任何方式影响持续传播。 如果您有一个意外可能会被 const 传播的函数,而 llvm 这样做了,并且您以不再可传播 const 的方式更改了该函数,则 llvm 将停止这样做。 这完全独立于将const附加到函数。 对于 llvm 来说, const fnfn之间没有区别。

同时,当您将const附加到fn时,rustc 根本不会改变它的行为(假设该函数是有效的 const fn 并且因此在这样做之后仍然可以编译)。 从现在开始,它只允许您在常量中调用此函数。

我不是在考虑 LLVM,而是在考虑生锈。 LLVM 在这里对我来说并不重要。

@SoniEx2

const fn 尽可能在编译时进行评估。

这是不正确的。 const fn ,当在特定上下文中调用时,将在编译时进行评估。 如果这不可能,则存在硬错误。 在所有其他情况下, const部分根本不重要。

需要const fn的此类上下文的示例是数组长度。 你可以写[i32; 15] 。 您也可以编写[i32; 3+4] ,因为编译器可以计算7 。 你不能写[i32; read_something_from_network()] ,因为这有什么意义? 有了这个提议,你可以写[i32; foo(15)] if foo is const fn ,这确保它更像是加法而不是访问网络。

这根本不是关于程序运行时可能或将要运行的代码。 没有“可能在编译时评估”。 只是“必须在编译时评估或中止编译”。

另请阅读 RFC: https ://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md

如果没有const fn注释,如果它是推断属性怎么办? 它不会在源代码中明确显示,但可以在自动生成的文档中自动标记。 这将允许最终扩大被认为是const的内容,而库作者无需更改他们的代码。 起初,推理可能仅限于const fn当前支持的任何内容(没有任何 let 绑定的纯确定性函数?)。

在这种方法下,如果结果绑定到const变量,则计算将在编译时进行,否则在运行时进行。 这似乎更可取,因为它让调用者(而不是被调用者)控制何时评估函数。

这已经被相当彻底地讨论过了。 这种方法的缺点是它更容易意外转向另一个方向 - 有人可能在const上下文中使用库函数,但库作者可能不再使用const甚至没有意识到。

嗯,是的,这是个问题……

编辑:我能想到的唯一解决方案,而不是一路走到const fn ,将有一个选择退出注释,以便图书馆作者可以保留打破const的权利尼斯。 不过,我不确定这是否比到处洒const fn更好。 唯一真正的好处是更快地采用const的扩展定义。

编辑2:不过,我想这会破坏向后兼容性,所以它不是首发。 对不起,旁道。

所以……讨论已经平息了。 让我们总结一下:

rfcbot 评论是https://github.com/rust-lang/rust/issues/24111#issuecomment -376649804

目前的担忧

  • 这不是优先事项,2018 年的发布已经在我们的盘子上放了足够多的东西
  • 我们开始标记所有东西const ,这很烦人
  • 这是我们想要承诺的设计吗?

我真的不能谈论优先事项+后果问题,除了const fn已经在夜间烘烤了很长时间

另外两点密切相关。 @eddyb的设计(我理解为 https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588 )是没有const fn ,而是有#[const]属性,你可以拍到东西:

#[const]
mod foo {
    pub fn square(i: i32) -> i32 { i * i }
}
#[const]
fn bar(s: &str) -> &str i{ s }
#[const]
fn boo() -> fn(u32) -> u32 { meh }
fn meh(u: u32) -> u32 { u + 1 }

并且递归地进入用它标记的任何内容,因此#[const]模块中的任何函数都是#[const] fn 。 在#[const] fn中声明的函数也是#[const] fn

这减少了所需的注释数量,因为一些 crate 只会在 lib.rs 中添加一个#![const] lib.rs并完成它。

我在该设计中看到的问题(但这些问题也经常存在于const fn中):

  • 可能需要选择退出支持,因为您可能希望能够在#[const]模块树的深处声明一些非常量函数。
  • 参数/返回类型位置中指向#[const] fn的函数指针是否必须为#[const] fn

    • 再次,选择退出是必要的

我们确实需要考虑这些事情,所以我们不会设计一个与未来版本不兼容的系统,我们希望能够在 const eval 期间通过函数指针调用函数。

请注意,我没有提出某种设计,而只是列出了一些已知的合理方向。
最初的想法是一个广义的“暴露函数体”属性,不限const ,但有许多可能的变化,其中一些甚至可能是好的

编辑:(不想忘记这一点) @solson向我展示了 Lean 如何具有像@pattern这样的属性,这些属性会自动从函数体中派生各种东西。

@oli-obk 我认为我们不应该使用属性,因为unsafe不使用属性。
此外, async目前也没有。 如果我们在给定try { .. }块的情况下引入try fn ,那么我们还有另一个不是基于属性的东西。 我认为我们应该尽量保持与效果类似的东西尽可能一致。 是否使用属性。 #[target_feature(..)]确实使整体一致性产生了皱纹。

PS:您可以使用const mod { .. }或多或少地获得与#![const]相同的效果。 这也适用于try modasync modunsafe mod

我总是倾向于用特殊类型做事。

struct SizeOf<T>;

impl<T> SizeOf<T> {
    const intrisic Result: usize;
}

使用现有语法学习新类型比使用新语法学习新概念更容易。

我们以后可以在运行时支持类型系统。

fn sq(v: i32) -> i32 {
    Square<v>::Result
}

编译时的类型,编译时或运行时的 const 泛型。

所以...我建议忽略这样一个事实,即可能有更好的设计,因为我们的设计是

  1. 容易推理
  2. 向前兼容任何更宽松的设计
  3. 历史上曾在不稳定的代码中获得巨大成功,主要抱怨是功能不足。
  4. 可以 linted 建议将注释添加到尚未注释的函数中,这些函数具有允许添加它的主体。
  5. 正如合并请求中所发布的,我个人认为,这是我们唯一可以在没有另一个多年试用期的情况下稳定的设计。
  6. 允许代码在运行时和 const eval 代码之间共享,而不需要为每个代码自定义代码

我们以后可以在运行时支持类型系统。

那是依赖类型,这很遥远,而在运行时调用const fn今天工作得很好。

@oli-obk 特征呢? 我不想稳定const fn而不知道我们将为特征方法做什么,这些方法仅在某些特征impl const fn

@eddyb似乎我应该加快编写新的 const 边界和方法。 :)

@Centril我的观点是,属性提议(无论是否使用关键字)会导致处理 trait 方法的方法更加不同,我们必须对此进行比较。
当前的const fn方法可能看起来简单且可扩展,但在实际扩展时并非如此

通用常量 + 常量泛型:

intrinsic const SizeOf<T>: usize;

const Pow<const V: usize>: usize = V*V;

@eddyb我想到了与 const fn 设计完全兼容的各种解决方案。 我会写下来。

哇,我刚看到已经两年了。 是否有任何可预见的稳定日期? 我有一个在stable上几乎可用的板条箱,因为它等待这个扩展被稳定。 :)

@rfcbot关注运行时指针地址

在另一个问题上,出现了我们是否希望const fn的引用透明性的问题,并且出现了原始指针地址被用作非确定性预言机的问题: https ://github.com/rust- 之前进行一些原始指针操作unsafe (不确定今天甚至允许其中有多少)。

@eddyb E0018 是否也适用于const fn s?

C方式是允许对象指针全部为0,除非是相对的(即在对象内部)并在运行时以某种方式进行跟踪。

我不确定 rust 是否支持 C 的别名规则。

@sgrif许多关于常量的错误迟早会消失 - miri 不关心值被视为的类型, usize值中的抽象位置仍然是抽象位置(并将其转换为指针会返回原始指针)。

我刚刚检查过,现在,将指针转换为整数和指针之间的比较运算符都在常量上下文中被禁止。 然而,这只是我们想到的,我还是很害怕。

@eddyb够公平的。 但是,我希望您对const fn的任何担忧今天已经影响到任何const块。

@sgrif不同的是const (甚至相关的const s 依赖于泛型类型参数)在编译时完全评估,在 miri 下,而const fn是非const fn用于运行时调用。
因此,如果我们真的想要引用透明性,我们需要确保我们不允许(至少在安全代码中)可能导致运行时不确定性的事情,即使它们在 miri 下是好的
这可能意味着获取浮点数也是一个问题,因为例如 NaN 有效载荷。

在美里你应该考虑做的事情:

除非是相对的,否则所有指针都是 0。 例如:

#[repr(C)]
struct X {
    a: usize,
    b: u8,
}
let x = X { a: 1, b: 2 };
let y: usize = 3;
assert_eq!(&x as *const _ as usize, 0);
assert_eq!(&x.a as *const _ as usize, 0);
assert_eq!(&x.b as *const _ as usize, 8);
assert_eq!(&y as *const _ as usize, 0);

然后你在 miri 评估中跟踪它们。 有些事情是 UB,比如从指针到使用再到指针,但这些很容易被禁止(禁止使用到指针转换。因为您已经在运行时/评估时跟踪指针)。

至于浮点位,NaN 归一化?

我认为这两者都会使整个事情具有确定性。

这可能意味着获取浮点数也是一个问题,因为例如 NaN 有效载荷。

我认为,为了完全透明的引用,我们必须让所有浮动操作都不安全。

浮点确定性很难

这里最重要的一点是 LLVM 的优化器可以并且确实改变了浮点操作的顺序,以及 ~performs~ 融合了它具有组合操作码的操作。 这些更改确实会影响操作的结果,即使实际差异很小。 这会影响引用透明度,因为 miri 执行非 llvm 优化的 mir,而目标执行 llvm 优化且可能重新排序并因此具有与本机代码不同的语义。

我现在同意第一个没有浮动的稳定 const fn 功能,直到决定引用透明​​度的重要性,但不希望 const fn 被该讨论减慢或阻止。

这里最重要的一点是 LLVM 的优化器可以并且确实改变了浮点操作的顺序,以及执行它具有组合操作码的熔断操作。 这些更改确实会影响操作的结果,即使实际差异很小。

如果没有快速数学标志,则不允许/不进行融合操作(我假设您指的是 mul-add),因为它会改变结果的舍入。 LLVM 在针对 IEEE 兼容硬件时非常小心地保留浮点运算的确切语义,在“默认浮点环境”设置的限制范围内。

LLVM 没有尝试保留的两件事是 NaN 有效负载和 NaN 的信号传递,因为在默认 fp 环境中无法观察到这两者——只能通过检查浮点数的位,因此我们需要禁止。 (即使 LLVM 对这些更加小心,硬件在处理 NaN 有效负载方面也会有所不同,因此您不能将其固定在 LLVM 上。)

我知道编译器的决定可以对浮点结果产生影响的另一个主要情况是x87 (SSE 之前)代码中溢出和重新加载的位置。 这主要是一个问题,因为 x87 默认情况下,中间结果舍入到 80 位,存储时舍入到 32 或 64 位。 在每条 FPU 指令之前正确设置舍入模式以实现正确舍入的结果是可能的,但这并不实际,因此(我相信)LLVM 不支持它。

完全参照透明

我的观点是,我们应该采用完全的引用透明性,因为它与 Rust 的整体信息相吻合,即选择安全性/正确性而不是便利性/完整性

参照透明性增加了许多推理的好处,例如启用等式推理(从根本上说,快速和松散的推理在道德上是正确的)。

但是,当然也有缺点。 失去完整性wrt。 CTFE。 我的意思是,引用透明的const fn机制在编译时无法像非引用透明的const fn方案那样评估。 对于那些建议不坚持引用透明度的人,我要求他们提供尽可能多的具体用例来反对这个提议,以便我们可以评估权衡。

好的,看来您对 LLVM 点的看法是正确的,除非您启用快速数学模式,否则它确实似乎避免了计算错误的操作。

但是,浮点运算仍然依赖于一些状态,比如内部精度。 CTFE 浮点评估器是否知道运行时的内部精度值?

此外,在将值溢出到内存期间,我们将内部值转换为 ieee754 格式,从而改变精度。 这也会影响结果,并且没有指定编译器执行溢出的算法,是吗?

@est31请注意,我一直假设我们不在乎编译时和运行时行为是否不同,只是重复调用(使用冻结的对象图)是一致的并且没有全局副作用。

因此,如果我们真的想要引用透明性,我们需要确保我们不允许(至少在安全代码中)可能导致运行时非确定性的事情,即使它们在 miri 下是可以的。

所以这里的目标是保证const fn在运行时是确定性和无副作用的,即使 miri 在执行期间会出错,至少如果const fn是完全安全的代码? 我从不认为这很重要,TBH。 这是一个非常强烈的要求,实际上我们至少必须让 ptr-to-int 和float-to-bits不安全,这很难解释。 OTOH,然后我们还应该使原始指针比较不安全,我会很高兴:D

这样做的动机是什么? 似乎我们正试图重新引入纯度和效果系统,这是 Rust 曾经拥有和失去的东西,大概是因为它们没有承载他们的重量(编辑:或者因为它不够灵活,无法完成所有不同的操作人们想要使用它的东西,请参阅 https://mail.mozilla.org/pipermail/rust-dev/2013-April/003926.html)。

如果 ptr-to-int 从对象的开头转换是安全的,并且您允许 int-to-ptr 失败。 这将是 100% 确定性的。 并且每次调用都会产生相同的结果。

ptr-to-int 是安全的,int-to-ptr 是不安全的。 在const评估中应该允许 ptr-to-int 得到“未执行”的结果。

你真的应该在解释器/虚拟机中使用 NaN 规范化。 (LuaJIT 就是一个例子,它使所有的 NaN 结果都是规范的 NaN,而其他所有的 NaN 都是压缩的 NaN)

如果 ptr-to-int 从对象的开头转换是安全的,并且您允许 int-to-ptr 失败。 这将是 100% 确定性的。 并且每次调用都会产生相同的结果。

你建议我们如何在运行时实现它? (&mut *Box::new(...)) as usize现在是一个不确定的函数,应该都是const fn 。 那么你建议我们如何确保它“在运行时被引用传递”,即总是返回相同的值?

Box::new 应该在 VM 中返回一个跟踪的指针。

转换将导致 0,在编译时(即在 const 评估中)。 在非常量评估中,它将返回任何内容。

跟踪指针的工作方式如下:

您分配一个指针,然后分配它。 当您分配它时,VM 将指针值设置为 0,但获取指针的内存地址,并将其附加到查找表。 当您使用指针时,您将浏览查找表。 当你得到指针的值(即组成它的字节)时,你得到0。假设它是一个对象的基指针。

Box::new 应该在 VM 中返回一个跟踪的指针。

我们在这里讨论的是运行时行为。 如在二进制文件中执行时会发生什么。 没有虚拟机。

miri 已经可以很好地完全确定地处理所有这些。


另外,回到对const fn和运行时确定性的关注:如果我们想要那个(我不确定我们是否这样做,仍在等待一些人向我解释我们为什么关心 :D ),我们可以声明ptr-to-int 和 float-to-bits 是非常量操作。 这有什么问题吗?

@RalfJung我已经在关注评论中链接了https://github.com/rust-lang/rust/issues/49146#issuecomment -386727325,可能在其他消息中丢失了 - 它包括对@Centril提出的解决方案的描述(进行操作unsafe并将一些“正确使用”列入白名单,例如offset_of用于 ptr-to-int 和 NaN-normalized float-to-bits)。

@eddyb当然,有多种解决方案; 我要的是动力。 我在那边也没找到。

另外,从 IRC 引用我自己的话:我认为我们甚至可以正确地争辩说 CTFE确定性的,即使允许 ptr-to-int 强制转换也是如此。 它仍然需要非 CTFE 代码(例如,提取 ptr 转换为 int 的位)来实际观察任何非确定性。

const fn thing() -> usize {
    (*Box::new(0)) as *const _ as usize
}

const X: usize = thing();
const Y: usize = thing();

X == Y?

根据我的建议,X == Y。

@SoniEx2打破foo as *const _ as usize as *const _现在完全是一个问题

写作动机:
我个人不认为运行时行为与编译时行为不同,甚至 const fn 在运行时是不确定的。 安全不是这里的问题,所以 unsafe 是完全错误的关键字 imo。 这只是我们在编译时 eval 完全捕捉到的令人惊讶的行为。 您已经可以在正常功能中执行此操作,所以这并不奇怪。 const fn 是关于在编译时将现有函数标记为可评估而不影响运行时。 这与纯度无关,至少当人们谈论“纯粹的地狱”时,我感受到了这种氛围(我不在,所以我可能会误解)

@SoniEx2是的,总是X == Y

但是,如果您有:

const fn oracle() -> bool { let x = 0; let y = &x as *const _ as usize; even(y) }

fn main() {
    assert_eq!(oracle(), oracle());
}

然后main可能会在运行时恐慌。

@RalfJung

或者是因为它不够灵活,无法完成人们想用它来做的所有不同事情 [..]

那些东西是什么? 如果没有具体化,很难评估这一点。

我认为我们甚至可以正确地争论 CTFE 是确定性的,即使允许 ptr-to-int 强制转换。
[...]
它仍然需要非 CTFE 代码(例如,提取 ptr 转换为 int 的位)来实际观察任何非确定性。

是的, const fn在编译时执行时仍然是确定性的,但我不认为它在运行时是确定性的,因为总会有一些非const fn (只是fn ) 代码,如果const fn的结果在运行时对任何事情都有用,那么fn代码将观察执行const fn的副作用。

Haskell 中纯代码和非纯代码之间的全部区别在于,您可以将“是否有副作用”作为本地决策,您不必考虑全局可能的副作用。 也就是说,给定:

reverse :: [a] -> [a]
reverse []       = []
reverse (x : xs) = reverse xs ++ [x]

putStrLn :: String -> IO ()
getLine :: IO String

main :: IO ()
main = do
    line <- getLine
    let revLine = reverse line
    putStrLn revLine

您知道let revLine = reverse line revLine的结果只能取决于传递给reverse的状态,即line 。 这提供了推理优势和清晰的分离。

我想知道当时的效果系统是什么样子的......我认为我们需要一个给定的const fnasync fn等,无论如何都要干净利落地完成它并获得代码重用(东西 https:// github.com/rust-lang/rfcs/pull/2237 但有一些变化......)和参数化似乎是一个好主意。

用 Phil Wadler 和 Conor McBride 的话来说:

我是纯洁的还是不纯洁的?
——菲利普·瓦德勒 [60]

我们说“是”:纯度是在当地做出的选择

https://arxiv.org/pdf/1611.09259.pdf

@oli-obk

我不羡慕必须记录这种令人惊讶的行为的人,即如果在运行时或编译时执行相同的参数, const fn的执行结果可能会有所不同。

作为一个保守的选择,我建议我们通过将const fn稳定为参照透明来延迟参照透明度/纯度的决定,使其unsafe (或非const )违反它,但我们实际上并不能保证引用透明度,所以人们也不能假设它。

稍后,当我们获得经验时,我们就可以做出决定。
获得经验包括人们试图利用非确定性但失败了,然后报告它并告诉我们他们的用例。

是的,但这不是 const 评估。

在 const 中将 int 转换为 ptr 似乎并不是一个巨大的损失。 获得场偏移似乎更重要,这就是我想要保留的。

在将const添加到函数@SoniEx2时,我不想默默地改变行为,这基本上就是您的建议。

@centril我同意,让我们现在做保守的事情。 所以我们不会使这些不安全,而是不稳定。 这样 libstd 可以使用它,但我们可以稍后决定细节。

我们确实遇到的一个问题是,用户总是可以通过引入联合和进行一些花哨的转换来阻止我们所做的任何分析,因此我们必须进行这样的规避转换 UB,以便我们以后可以更改它。

在运行时它仍然是相同的,const 只会在 const 时间改变事情,这......甚至不可能没有 const。

const fns 可以成为具有不同指针别名规则的子语言。

@Centril @est31我不确定三氟氯乙烯与任何事情有什么关系(我们可以避免使用一堆首字母缩略词而不定义它们吗?)

@sgrif CTFE(编译时函数评估)长期以来一直用于 Rust(包括该线程的多年历史部分)。 这是应该在某个中心位置定义的术语之一。

@sgrif哈哈抱歉,大多数时候我都是想知道“他们现在使用哪些奇怪的词?”的人。 当他提到“CTFE”时,我想我是在与 IRC 中的@eddyb交谈,我不得不问他这是什么意思:p。

此页面上的@eddyb ctrl+f + CTFE 不会导致任何定义它。 无论哪种方式,我大多只是假设我们不想强迫人们做很多挖掘来参与这里的讨论。 “你应该已经知道这意味着什么”是相当排斥的 IMO。

@Centril @est31谢谢 :heart:

所以...任何关于

我们确实遇到的一个问题是,用户总是可以通过引入联合和进行一些花哨的转换来阻止我们所做的任何分析,因此我们必须进行这样的规避转换 UB,以便我们以后可以更改它。

const fn 内部的联合字段访问是否也因此不稳定?

此页面上的 ctrl+f + CTFE 不会导致任何定义它。

抱歉,我的意思是,它在 Rust 设计和开发中的使用早于这个 1.0 之前的问题。
这些术语应该有一个中心文件,但遗憾的是,参考的词汇表确实缺乏。
HRTB 和 NLL 是其他首字母缩略词的两个例子,它们较新,但也没有内联解释。

我不希望任何讨论具有排他性,但我认为要求@Centril@est31定义 CTFE 是不公平的,因为他们都没有首先引入首字母缩略词。
遗憾的是,有一个解决方案不一定适用于 GitHub,我在某个 subreddit 中看到过,其中机器人将创建顶级评论并保持更新,并列出所有常用首字母缩略词的扩展列表讨论中出现的那个subreddit。

另外,我很好奇我是否处于谷歌泡沫中,因为 CTFE 的两篇维基百科文章(“三氟氯乙烯”和“编译时函数执行”)是我的前两个结果。 即使这样,前者也以“对于编译器功能,请参阅编译时函数执行。”开头。

我把 CTFE wiki 链接放在顶部;现在我们可以回到const fn s 吗?:P)

有人可以将 CTFE 添加到 rustc 指南词汇表(我在移动设备上)吗?

另外,我认为我们应该稳定最低限度,看看人们在实践中遇到了很多问题,哪些是使用 const if 和 match 表达式的当前方法。

@Centril

我并不羡慕必须记录这种令人惊讶的行为的人,即如果在运行时或在编译时执行相同的参数,执行结果可能会因 const fn 不同而有所不同。

如果在 CTFE 时间执行,您上面编写的even将失败,因为它检查指针的位。 (虽然实际上我们可以确定地说这是因为对齐,并且如果通过位操作完成均匀性测试,“full miri”会正确地做到这一点。但是假设您正在测试整个最低有效字节,而不仅仅是最后一点。)
我们在这里讨论的情况是一个函数在编译时出现解释器错误,但在运行时成功。 区别在于函数是否完成执行。 我认为这并不难解释。

我同意,如果 CTFE 以 result 成功,那么该函数的运行时版本也应保证以相同的结果成功。 但这比我们在这里谈论的要弱得多,不是吗?

@oli-obk

我同意,让我们现在做保守的事情。 所以我们不会使这些不安全,而是不稳定。 这样 libstd 可以使用它,但我们可以稍后决定细节。

我失去了上下文,这里的“这些”是什么?

现在,幸运的是,CTFE miri 完全拒绝对指针值做任何事情——算术、比较、所有错误。 这是在 CTFE 时间根据计算中实际使用的值进行的检查,它不能被联合规避,而且无论如何进行算术/比较所需的代码都不存在。 因此,我很确定我们满足了我上面所说的保证。

如果我们让 CTFE 返回一个指针值,我可以想象会出现问题,但是编译时计算的指针值如何在任何地方都有意义呢? 我假设我们已经检查了任何 miri 计算的内容是否不包含指针值,因为我们必须将其转换为位?

我们可以小心地向 CTFE miri 添加操作,事实上, @eddyb的 offset_of [1] 所需要的只是指针减法。 该代码存在于“full miri”中,并且只有在两个指针都在同一个分配中时它才会成功,这足以维持上述保证。 @eddyb添加的作为保障措施的assert是行不通的。
如果操作只影响指针的对齐部分,我们还可以允许对指针值进行位操作,这仍然是确定性的,并且代码实际上已经存在于“full miri”中。

编辑:[1] 作为参考,我在这个线程中指的是他的宏,我们无法链接到,因为它被标记为离题,所以这是一个副本:

macro_rules! offset_of {
    ($Struct:path, $field:ident) => ({
        // Using a separate function to minimize unhygienic hazards
        // (e.g. unsafety of #[repr(packed)] field borrows).
        // Uncomment `const` when `const fn`s can juggle pointers.
        /*const*/ fn offset() -> usize {
            let u = $crate::mem::MaybeUninit::<$Struct>::uninit();
            // Use pattern-matching to avoid accidentally going through Deref.
            let &$Struct { $field: ref f, .. } = unsafe { &*u.as_ptr() };
            let o = (f as *const _ as usize).wrapping_sub(&u as *const _ as usize);
            // Triple check that we are within `u` still.
            assert!((0..=$crate::mem::size_of_val(&u)).contains(&o));
            o
        }
        offset()
    })
}

EDIT2:实际上,他也在这里发布了它。

“这些”是浮点->位转换和指针->使用转换

我同意如果 CTFE 成功并获得结果,那么该函数的运行时版本也应保证成功获得相同的结果。 但这比我们在这里谈论的要弱得多,不是吗?

因此,在运行时使用参数调用的函数只有在编译时使用相同参数进行评估时实际终止时才能保证纯度?

这将使我们的生活变得轻松一百万倍,特别是因为我看不到一种方法可以防止不确定性,同时又不会留下漏洞或以颠覆性的方式削弱 const fn。

因此,在运行时使用参数调用的函数只有在编译时使用相同参数进行评估时实际终止时才能保证纯度?

是的,这就是我所提议的。


再想一想(并阅读我写这篇文章时出现的@oli-obk 的回复),我觉得你想要的是一个额外的保证,类似于“一个安全的const fn不会出错使用有效参数调用时的 CTFE 时间(恐慌除外)”。 某种“常量安全”保证。 连同我上面提到的关于成功的 CTFE 与运行时行为一致的保证,这将保证安全的const fn在运行时是确定性的,因为它与成功的 CTFE 执行相匹配。

我同意这是一个更难获得的保证。 无论好坏,Rust 都有各种安全操作,CTFE miri 无法保证在保持确定性的同时始终成功执行,例如@Centriloracle的变体,它测试最低有效字节为 0。从角度来看在此设置中的 CTFE 中,“ usize类型的有效值”构成PrimVal::Bytes的值,而不应允许PrimVal::Ptr [1]。 就像我们在 const 上下文中有一个稍微不同的类型系统——我不是建议我们改变 miri 所做的事情,我建议我们改变各种 Rust 类型在附加到const fn时的“含义”。 这样的类型系统将保证所有安全算术和位运算在 CTFE 中不会出错:对于 CTFE 有效的输入,整数减法在 CTFE 中永远不会失败,因为双方都是PrimVal::Bytes 。 当然, ptr-to-int 在此设置中必须是不安全的操作,因为它的返回值类型usize但不是 CTFE 有效的usize

如果这是我们关心的保证,那么在检查 CTFE 函数时让类型系统更加严格,对我来说似乎不是不合理的; 毕竟,我们想用它来做更多的事情(检查“const 安全性”)。 然后,我认为在const上下文中声明 ptr-to-int 强制转换unsafe会很有意义,认为这是必要的,因为const上下文使额外的保证。

就像我们正常的“运行时安全”可以被不安全的代码颠覆一样,“const 安全”也可以,这很好。 我没有看到不安全代码仍然能够通过联合执行 ptr-to-int 强制转换的任何问题——毕竟这是不安全代码。 在这个世界上,const 上下文中不安全代码的证明义务比非 const 上下文中的要强; 如果您的函数返回一个整数,您必须证明这将始终是PrimVal::Bytes并且永远不会是PrimVal::Ptr

@eddyb的宏需要一个不安全的块,但它仍然可以安全使用,因为它只使用这些“const 不安全功能”(ptr-to-usize 然后减去结果,或者只使用指针减法内在直接)以保证不会引发 CTFE 错误的方式。

这种系统的代价是安全的高阶const fn必须采用const fn闭包才能保证执行闭包本身不会违反“const 安全性”。 这就是实际获得有关安全const fn的适当保证的代价。

[1] 我在这里完全忽略浮动,因为我不太了解非确定性会出现在哪里。 有人可以提供一个浮点代码示例,它在 CTFE 时的行为与在运行时的行为不同吗? 如果在进行浮点运算时,如果其中一个操作数是信号 NaN(以获得第一个保证,即我以前的帖子中的保证),并且说 CTFE 类型系统,是否足以使 miri 错误不允许以f32 / f64类型发出 NaN 信号(以获得“常量安全”)?

@eddyb作为保障添加的断言是行不通的。

当然可以,但是您现在可以将assert!(condition);重写为[()][!condition as usize];

当然可以,但是你可以重写 assert!(condition); 到 [()][!condition as usize]; 目前。

这不是我想的断言,它是您条件下的指针相等测试。 指针相等是邪恶的,如果我们不能在 CTFE 中允许它,我会更喜欢。

编辑:没关系,我刚刚意识到assert测试了偏移量。 所以实际上它在 CTFE 时间永远不会失败,因为如果在执行wrapping_sub时指针不在同一个块中,miri 将出错。

// guess we can't have this as const fn
fn is_eq<T>(a: &T, b: &T) -> bool {
    a as *const _ == b as *const _
}

正如我之前所说,在 miri 中使用虚拟指针,而不是真实指针。 它可以在 const 时间提供 const 确定性。 如果函数编写正确,则运行时和编译时行为应该产生相同的结果,而不管运行时是非确定性的,而编译时是确定性的。 如果您为其编码,您可以在非确定性环境中具有确定性行为。

获得字段偏移量是确定性的。 使用虚拟指针,它保持确定性。 有了真正的指针,它仍然是确定性的。

获得指针第 14 位的偶数不是确定性的。 使用虚拟指针,它变得确定性。 使用真正的指针,它不是确定性的。 这很好,因为一个发生在编译时(确定性环境),而另一个发生在运行时(非确定性环境)。

const fn 应该与它们所使用的环境一样具有确定性。

@SoniEx2

// guess we can't have this as const fn

确实我们不能。 我想我可以接受原始指针比较的一个版本,如果任何一个指针当前在分配的对象中不可取消引用(但这不是“完整的 miri”当前实现的),则该版本会出错。 但是,这仍然会使is_eq不是“const 安全的”,因为如果T大小为零,即使我们只考虑安全代码,它也可能指向对象末尾的 1。

C++ 允许比较指针末尾的一个以产生不确定的(认为:非确定性的)结果。 C 和 C++ 都允许比较悬空指针以产生不确定的结果。 目前尚不清楚 LLVM 保证什么,但我宁愿不赌超过他们必须为 C/C++ 保证的保证(两者中较弱的,如果它们不同的话)。 如果我们想保证在 CTFE 中成功执行的所有内容的运行时确定性,这是一个问题,我认为我们会这样做。

@RalfJung

区别在于函数是否完成执行。

魔鬼提倡: “返回⊥”在一种情况下是一样的,因为它有不同的结果。

我认为这并不难解释。

我个人认为这是令人惊讶的行为; 你可以解释一下,我也可以理解(但我不代表……),但这不符合我的直觉。

@oli-obk

因此,在运行时使用参数调用的函数只有在编译时使用相同参数进行评估时实际终止时才能保证纯度?

就个人而言,我认为这种保证还不够。 我认为我们应该首先看看我们在纯度方面能走多远,只有当我们知道它在实践中会变得严重时,我们才应该转向更弱的保证。

@RalfJung

这将保证安全的 const fn 在运行时是确定性的,因为它与成功的 CTFE 执行相匹配。

好的; 你失去了我; 鉴于这两个前提,我不明白您是如何得出这种“安全的 const fn 是确定性的”保证的; 你能详细说明原因吗?

如果这是我们关心的保证,那么在检查 CTFE 函数时让类型系统更加严格,对我来说似乎不是不合理的; 毕竟,我们想用它来做更多的事情(检查“const 安全性”)。 因此,我认为在 const 上下文中声明 ptr-to-int 强制转换是不安全的,认为这是必要的,因为 const 上下文提供了额外的保证。

就像我们正常的“运行时安全”可以被不安全的代码颠覆一样,“const 安全”也可以,这很好。 我没有看到不安全代码仍然能够通过联合进行 ptr-to-int 强制转换的任何问题——毕竟这是不安全的代码。 在这个世界上,const 上下文中不安全代码的证明义务比非 const 上下文中的要强; 如果您的函数返回一个整数,您必须证明这将始终是PrimVal::Bytes并且永远不会是PrimVal::Ptr

这些段落对我来说是音乐! ❤️ 这似乎确实确保了确定性(“纯度”)? 这正是我之前想到的。 我认为const 安全性也是一个了不起的术语!

为了将来参考,让我将此保证称为“CTFE 健全性”:如果 CTFE 没有出错,那么它的行为与运行时匹配——两者都发散,或者都以相同的值结束。 (我在这里完全忽略了高阶返回值。)

@Centril

魔鬼提倡:“返回⊥”在一种情况下是一样的,因为它有不同的结果。

嗯,这显然是一个定义问题。 我认为您理解我所描述的 CTFE 稳健性保证,并且您同意这是我们想要的保证; 这是否是我们想要的全部有待讨论:)

好的; 你失去了我; 鉴于这两个前提,我不明白您是如何得出这种“安全的 const fn 是确定性的”保证的; 你能详细说明原因吗?

假设我们有一些对foo(x)的调用,其中foo是一个安全的 const 函数,而x是一个 const-valid 值(即,它不是&y as *const _ as usize )。 然后我们知道foo(x)将在 CTFE 中执行而不会引发错误,通过 const 安全性。 因此,根据 CTFE 的稳健性,在运行时foo(x)的行为方式与它在 CTFE 中的行为方式相同。

本质上,我认为我将您的保证分解为两部分——一个确保安全的 const fn 永远不会尝试做 CTFE 不支持的事情(比如从标准输入读取,或确定指针的最低有效字节是否为 0),一个确保 CTFE 支持的任何内容与运行时匹配。

这些段落对我来说是音乐! 心脏 这似乎确实确保了确定性(“纯度”)? 这正是我之前想到的。 我认为 const 安全性也是一个了不起的术语!

很高兴你喜欢它。 :) 这意味着我终于明白我们在说什么了。 “纯度”可以有很多不同的含义,当我使用这个词时,我常常感到有些不安。 并且确定性不是const 安全的充分条件,相关标准是 CTFE 中的执行是否会引发错误。 (确定性非 const-safe 函数的一个示例是我的orcale变体乘以 0。即使使用不安全代码也不能这样做,因为 miri 在检查指针字节时会出错,即使字节最终无关紧要。就像提取指针的最低有效字节的操作是“const-UB”,因此即使在不安全的 const 代码中也是不允许的。)

是的,一个超过数组元素末尾的指针可能会指向下一个数组元素。 所以呢? 这不是真的不确定吗? 无论如何,编译时的确定性才是最重要的。 据我所知,运行时评估可能会因堆栈耗尽而出现段错误。

@RalfJung

这种系统的代价是安全的高阶const fn必须采用 const fn 闭包才能保证执行闭包本身不会违反“const 安全性”。 这就是实际获得有关安全const fn的适当保证的代价。

我相信通过引入?const可以严重缓解这种情况,以支持转换大多数现有代码,您可以在其中编写高阶函数,其结果可以绑定到const当且仅当提供的函数是?const fn(T) -> Uis_const(x : T) ; 所以你有了:

?const fn twice(fun: ?const fn(u8) -> u8) { fun(fun(42)) }

fn id_impure(x: u8) -> u8 { x }
const fn id_const(x: u8) -> u8 { x }
?const fn id_maybe_const(x: u8) -> u8 { x }

fn main() {
    let a = twice(id_impure); // OK!
    const b = twice(id_impure); // ERR!
    let c = twice(id_const); // OK!
    const d = twice(id_const); // OK!
    let e = twice(id_maybe_const); // OK!
    const f = twice(id_maybe_const); // OK!
}

我会在一周左右的时间里写一个 RFC 提出一些关于这个效果(甚至更多)的东西。

@Centril此时您正在开发一个具有效果多态性的效果系统。 我知道这一直是你的秘密(?)议程,只是让你知道它变得非常明显。^^

@RalfJung我去年已经在 https://github.com/rust-lang/rfcs/pull/2237 透露了这个秘密,但我必须重写它;)
现在几乎是公共领域^,-

@SoniEx2

是的,一个超过数组元素末尾的指针可能会指向下一个数组元素。 所以呢? 这不是真的不确定吗? 无论如何,编译时的确定性才是最重要的。 据我所知,运行时评估可能会因堆栈耗尽而出现段错误。

问题出现在以下情况(在 C++ 中):

int x[2];
int y; // let's assume y is put right after the end of x in the stack frame
if (&x[0] + 2 == &y) {
  // ...
}

C 编译器想要(并且做!)优化与false的比较。 毕竟,一个指针指向x ,一个指向y ,所以它们永远不可能相等。
当然,除了机器上的地址相等的,因为一个指针指向x的末尾,它与y的(开头)相同的地址! 因此,如果您将代码模糊到足以使编译器看不到地址的来源,则可以判断比较结果为true 。 因此,C++ 标准允许两种结果都不确定地出现,从而证明优化(即false )和汇编编译(即true )的合理性。 C 标准不允许这样做,使 LLVM(和 GCC)不符合标准的编译器,因为两者都将执行这些类型的优化。

我在此线程和/或 IRC 的相关讨论中总结了我对 const 安全性、const soundness 等的想法: https ://www.ralfj.de/blog/2018/07/19/

这个问题在这里变得有些难以解开,因为已经讨论了很多事情。 @oli-obk 为 const-eval 问题创建了一个仓库,因此讨论特定子问题的好地方可能是https://github.com/rust-rfcs/const-eval 的问题跟踪器。

@Centril建议稳定一个与任何未来扩展兼容的最小版本:

  • 没有具有特征界限的通用参数
  • 没有fn指针或dyn Trait类型的参数或返回类型

    • 递归检查参数类型,因此参数字段也可能不是这些类型

  • 没有不安全的代码(因为我们不知道那里是否有问题)

    • 我个人认为这很好,除了union已经在一个额外的功能门之后,以及原始指针 derefs (现在通常禁止在任何常量中使用)。 任何其他不安全的代码都需要通过其他不安全的 const fns 或 const 内在函数,这需要他们自己讨论 wrt 稳定性。

(nit:我的建议还包括对fn指针或dyn Trait的返回类型的const fn s 的递归检查)

没有具有特征界限的通用参数

澄清一下,这样的事情会被接受吗?

struct Mutex<T> where T: Send { /* .. */ }

impl<T> Mutex<T> where T: Send {
    pub const fn new(val: T) -> Self { /* .. */ }
}

边界不是const fn本身的一部分。

还要澄清一下:是稳定这些东西并将其余部分留在功能门之后的提议,还是稳定这些并将其余部分完全变成错误?

@mark-im 其余的将留在功能门后面。

@oli-obk 不安全代码有什么问题? 我们确实允许在const X : Ty = ...中使用 unsafe ,这具有所有相同的问题。 我认为const fn实体应该像const实体一样进行检查。

我认为我们确实希望在“非常量”操作方面保持保守——安全但不是 const 安全的操作(基本上是原始指针上的任何操作)——但在 const 上下文中已经完全不允许这些操作,对吧?

边界不是 const fn 本身的一部分。

不,该提案也不允许对 impl 块进行限制

不安全代码有什么问题?

我没有看到我的评论中提到的任何问题。 无论如何,每个 const 不安全特性/功能都需要经过自己的稳定化。

@RalfJung我认为问题是@Centril很紧张,因为我们错过了一些东西。那些在 const 上下文中已经完全被禁止了”。 ;) 但是我们必须在某个时候将unsafe { .. }稳定在const fn中,所以如果您确定没有问题并且我们捕获了所有非 const 操作,那么让我们开始吧?

此外,如果我们错过了什么,我们已经有点搞砸了,因为人们可以在const上使用它。

我仍然打算写一个 PR 来填写const fn RFC repo的 const 安全/推广部分; 那是时候仔细检查我们是否涵盖了所有内容(并有测试用例)。

Discord 上出现的另一件事是 FP 操作:我们目前无法保证它们会匹配真实硬件。 CTFE 将完全遵循 IEEE,但 LLVM/硬件可能不会。

这也适用于const项目,但它们永远不会在运行时执行——而const fn可能是。 因此,不要在const fn中稳定 FP 操作似乎是谨慎的。

OTOH,我们已经推广了FP操作的结果? 因此,我们已经在 stable 上观察到了运行时/编译时不匹配。 是否值得进行一次火山口运行,看看我们是否可以撤消它?

为了将来参考,以下文章与浮点和确定性有关:

@RalfJung

是否值得进行一次火山口运行,看看我们是否可以撤消它?

如果我们能做到这一点,我会感到惊讶,但至少值得一试。 :)

@RalfJung在此线程中可能会有更有趣的输入https://github.com/rust-lang/rust/issues/24111#issuecomment -386764565

假设我们想保留关键字形式const fn ,我认为现在稳定一些东西,这已经足够有限了,是一个相当不错的解决方案(我以前怎么没看到它?!)

当我们做这些零碎的稳定时,我只想注册一个
要求提供明确的限制清单及其理由。 它是
当用户做出看似无害的更改并且
遇到一个编译错误,所以我们至少可以使错误变得好。 一世
认识到推理散布在许多讨论中,并非所有讨论
我已经关注了,但我认为文档中应该有一个表格(或
参考,或至少是 nomicon)列出每个不允许的操作,
它可能导致的问题,以及稳定的前景(例如“从不”,
“如果 RFC XYZ 被实施”,“在我们确定这部分规范之后”)。

2018 年 8 月 20 日星期一下午 1:44 Eduard-Mihai Burtescu <
通知@github.com> 写道:

假设我们想保持关键字形式 const fn,我认为稳定
现在的东西,足够有限,是一个相当不错的解决方案(如何
我以前没见过吗?!)


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/rust-lang/rust/issues/24111#issuecomment-414403036
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAC3n80yVxIa3agsJP4wXZFkgyHVmAsjks5uSvWXgaJpZM4D66IA
.

@est31正如@rkruppe已经写的那样,没有-ffast-math执行这些熔断器是非法的——我认为 LLVM 正确处理了这一点。

据我回忆,基本算术甚至不是那么成问题(除了在 32 位 x86 上,因为 x87 ......),但超越函数是。 那些不是const fn ,对吧? 所以我希望最终我们可以在这方面在const项目、促销和const fn之间实现平等。

@RalfJung我仍然不相信例如溢出然后在某些操作之间将其加载回FPU寄存器会产生与相同计算相同的结果而没有溢出。

据我回忆,基本算术甚至不是那么成问题(除了在 32 位 x86 上,因为 x87 ......),但超越函数是。

超越函数如何意味着一个问题?

IIRC,虽然常见的 x86 处理器支持一些先验功能,但这些功能很慢且被避开,并且仅包含完整性和与现有实现的兼容性。 因此,几乎在任何地方,超越函数都是用基本算术函数的组合来表示的。 这意味着它们的引用透明度与算术函数的引用透明度没有区别。 如果基本函数是“安全的”,那么建立在它们之上的任何东西都是“安全的”,包括超越函数。 这里“引用不透明”的唯一来源可能是那些超越函数在那些基本算术函数方面的不同近似(实现)。 这是问题的根源吗?

@est31虽然大多数超越函数最终只是由原始整数和浮点操作组成的库代码,但这些实现不是标准化的,实际上,Rust 程序可以在其整个生命周期内与三个不同的实现交互,其中一些也因主机或目标而异平台:

  • 程序在目标上使用的运行时实现(例如目标平台 libm,或某些情况下的硬件指令)
  • 有 LLVM 使用的常量文件夹(显然这只是主机平台 C libm)
  • MIRI 可以对这些操作做任何事情(例如, rustc_apfloat中的某些内容,或解释 Rust 实现,例如 https://github.com/japaric/libm/)

如果其中任何一个相互不同意,您可以根据表达式的评估时间得到不同的结果。

@rfcbot取消

我们不太可能在不久的将来稳定整个 monty。
相反,我想为一个更小的子集(大致在 https://github.com/rust-lang/rust/issues/24111#issuecomment-414310119 中概述)达成共识,我们希望在短期内稳定下来.
这个子集在#53555 中被跟踪。 那里有进一步的描述。

@Centril提案已取消。

@rkruppe是否有理由将超越函数降低为 llvm 内在函数? 难道我们不能通过将它们降低到我们控制并且在所有平台上都相同的众所周知的、仅 rust 的实现来避免整个问题吗?

是否有理由将超越函数降低为 llvm 内在函数?

除了不实现整个跨平台 libm 的简单性之外,内在函数在 LLVM 的优化器和代码生成中具有普通库函数无法获得的优势。 显然,在这种情况下,恒定折叠(以及相关的东西,如值范围分析)是一个问题,但在其他情况下它非常有用。 还有代数恒等式(由 SimplifyLibCalls pass 应用)。 最后,一些函数(主要是 sqrt 和它的倒数,它们不是超越的而是其他的)具有特殊的代码生成支持,例如在带有 SSE 的 x86 上生成sqrtss

难道我们不能通过将它们降低到我们控制并且在所有平台上都相同的众所周知的、仅 rust 的实现来避免整个问题吗?

即使忽略上述所有内容,这也不是 IMO 的好选择。 如果目标平台有 C libm,应该可以使用它,因为它更优化或避免膨胀。

内在函数在 LLVM 的优化器和代码生成中具有普通库函数无法获得的优势。

我认为只有打开快速数学才能启用此类优化?

如果目标平台有 C libm,应该可以使用它,因为它更优化或避免膨胀。

当然。 应该总是有一个选择来交换这个相当学术的属性——引用透明度——以支持更重要的事情,比如提高编译二进制文件的速度或更小的二进制文件。 例如,通过使用平台libm或打开快速数学模式。 还是您建议我们应该永远禁止快速数学模式?

IMO 我们不应该在 const 上下文中禁止f32::sin() ,至少如果我们允许+-等则不允许。这样的禁令将迫使人们创建和使用 crates提供与 const 兼容的实现。

我认为只有打开快速数学才能启用此类优化?

在没有 -ffast-math 的情况下,对这些函数的不断评估和sqrtss的发射很容易证明是正确的,因为它可以正确地四舍五入。

还是您建议我们应该永远禁止快速数学模式?

我没有任何建议,我也没有关于是否应该保证这样的财产的意见(atm)。 我只是在报告约束。

在 const fn 上下文中找不到由Vec引起的 ICE 未解决问题。

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=508238a9f06fd85720307bf6cc227586

我应该为此打开一个新问题吗?

@Voultapher是的,它看起来像一个新的 ICE。

好的,打开 #55063。

如果编译器能够检查当用户使用const fn注释函数时是否可以在编译时 constexpr 中调用它,为什么不自动对所有函数执行检查? (类似于自动特征)? 我想不出任何真正的不利影响,明显的好处是我们不必依赖容易出错的人类判断。

主要的缺点是它变成了一个公开的细节,所以一个实现
不应该是 const 的函数的更改现在正在中断。

我们也不需要依赖人类的判断。 我们可以有一个clippy lint,它告诉我们一个未注释的函数何时可能是const fnhttps ://github.com/rust-lang/rust-clippy/issues/2440

这类似于我们不推断局部变量的可变性,而是让编译器告诉我们在哪里添加或删除mut

@remexre const fn充当接口规范。 我对这个功能的微小细节不是很熟悉(也许下面的内容已经考虑过了),但是我可以想到编译器告诉函数何时被错误地注释为const的两种情况是如果此类函数将&mut作为参数或调用其他非const函数,则编译失败。 因此,如果您更改了const fn的实现并打破了这些限制,编译器将阻止您。 然后,您可以选择在 (a) 单独的函数中实现非const位,或者如果这是有意的更改,则破坏 API。

还有一个我没有看到讨论过的中间点,当它没有明确设置时,有可能引入这个标记的对立面和某种“函数纯度推断”。 然后文档会显示实际的标记,但如果它是const ,则会显示一些关于不保证该标记稳定性的警告。 问题是这可能会鼓励懒惰并且几乎每次都这样做,这不是它的目的。

const fn 应该能够产生输出吗? 为什么要禁止&mut

@aledomu我的评论是针对@AGaussman; 我说的是库作者公开了一个不是“本来应该是” const 的函数(因为 const-ness 不打算成为 API 的一部分)的情况; 如果要推断出 const,那么使所述函数成为非 const 将是一个重大更改。

@SoniEx2 const fn是一个可以在编译时评估的函数,这恰好是任何纯函数的情况。

@remexre如果它不是 API 的稳定部分,请不要标记它。

对于我评论的推理位,这就是为什么我提到需要对 crate 文档进行一些警告。

有什么不同? 绝对没有!

const fn add_1(x: &mut i32) { x += 1; }
let mut x = 0;
add_1(&mut x);
assert_eq!(x, 1);
x = 0;
add_1(&mut x);
assert_eq!(x, 1);

const fn added_1(x: i32) -> i32 { x + 1 }
let mut x = 0;
x = added_1(x);
assert_eq!(x, 1);
x = 0;
x = added_1(x);
assert_eq!(x, 1);

我已针对以下问题提交了针对性问题:

  • 浮点数 + const eval: https ://github.com/rust-lang/rust/issues/57241
  • 与模式集成: https ://github.com/rust-lang/rust/issues/57240

以下目标问题已经存在:

  • 恐慌: https ://github.com/rust-lang/rust/issues/51999
  • 循环: https ://github.com/rust-lang/rust/issues/52000
  • 控制流程: https ://github.com/rust-lang/rust/issues/49146
  • 比较原始指针: https ://github.com/rust-lang/rust/issues/53020
  • 取消引用原始指针: https ://github.com/rust-lang/rust/issues/51911
  • 指向usize的原始指针: https ://github.com/rust-lang/rust/issues/51910
  • 联合字段访问: https ://github.com/rust-lang/rust/issues/51909
  • 长期运行评估的警告: https ://github.com/rust-lang/rust/issues/49980
  • 持续传播不会导致错误: https ://github.com/rust-lang/rust/issues/28238
  • &mut T参考和借用: https ://github.com/rust-lang/rust/issues/57349

如果还有其他尚未被其他问题跟踪的领域,则需要进行讨论。 const eval 和const fn ,我建议人们提出新问题(并在其中抄送我 + @oli-obk)。

这个问题的用处到此结束,特此关闭。

我没有考虑到所有细节,但是在min_const_fn中是否还有更多由 miri 支持但尚未启用的功能? 例如原始指针。

@SimonSapin是的,很好。 还有一些现有的问题。 我已经更新了评论 + 问题描述。 如果您遇到了未涵盖的内容,请提出新问题。

我认为当一个元跟踪问题完全不清楚它所涵盖的内容是否被更具体的问题详尽地涵盖时,关闭元跟踪问题是不合适的。

当我在 Servo 中删除#![feature(const_fn)]时,错误消息是:

  • trait bounds other than `Sized` on const fn parameters are unstable
  • function pointers in const fn are unstable

(这些const fn都是具有私有字段的类型的普通构造函数。前一条消息在struct Guard<T: Clone + Copy>的构造函数上,即使在构造函数中没有使用Clone 。后者用于将Option<fn()> (简化)初始化为NoneSome(name_of_a_function_item) 。)

但是,此问题的描述中既没有提到特征也没有提到函数指针类型。

我并不是说我们应该只针对上述问题提出两个更具体的问题。 我的意思是我们应该重新打开这个,直到我们以某种方式确保const_fn功能门后面的所有内容(仍然在错误消息中指向此处)都存在跟踪问题。 或者直到const_fn完全稳定。

@SimonSapin

我认为当一个元跟踪问题完全不清楚它所涵盖的内容是否被更具体的问题详尽地涵盖时,关闭元跟踪问题是不合适的。

这个问题有https://github.com/rust-lang/rust/issues/34511的味道,这是就跟踪问题而言最大的混乱之一。 一段时间以来,这个问题也一直是免费的,所以它现在不作为一个元问题。 对于这种免费的,请改用http://internals.rust-lang.org/

但是,此问题的描述中既没有提到特征也没有提到函数指针类型。

我并不是说我们应该只针对上述问题提出两个更具体的问题。

这正是我认为应该做的。 从 T-Lang 分类的角度来看,有针对性和可操作的问题是有利的。

我的意思是我们应该重新打开这个,直到我们以某种方式确保const_fn功能门后面的所有内容(仍然在错误消息中指向这里)都存在跟踪问题。 或者直到const_fn完全稳定。

我什至不清楚const_fn功能门甚至构成了什么,或者它会在某个时候全部稳定下来。 除了原始 RFC 中的边界和函数指针之外的所有内容都有未解决的问题,然后还有一些问题。

我什至不清楚功能门的 const_fn 是什么

这就是为什么我们不应该关闭它,直到我们弄清楚这一点,IMO。

一切

然而,这真的是一切吗?

相关: https ://github.com/rust-lang/rust/issues/57261

有人知道const_string_new功能发生了什么吗? 它有跟踪问题吗? 不稳定的书只是在这里链接。

@phansch那是因为所有rustc_const_unstable都指向这里。 (cc @oli-obk 我们可以解决这个问题吗?)

这个问题应该是开放的。 作为用户被指出是侮辱
到一个封闭的问题。

2019 年 1 月 9 日星期三 04:05 Mazdak Farrokhzad < [email protected]
写道:

@phansch https://github.com/phansch那是因为所有
rustc_const_unstable 点在这里。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/rust-lang/rust/issues/24111#issuecomment-452622097 ,
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAC3n7JhzsZZpizmWlp0Nww5bcfIqH2Vks5vBbC8gaJpZM4D66IA
.

@durka :总会有一个可能的窗口,每晚都会关闭某些东西,而分辨率仍然没有稳定下来。 这怎么侮辱?

我一直拒绝在这里发表评论,也许我们应该把这个对话转移到一个关于内部的线程(已经有一个吗?)但是......

关闭它的决定对我来说毫无意义。 这是一个跟踪问题,因为它出现在编译器的错误消息中,而且并不孤单,请参阅这篇文章以获取更多示例: https ://internals.rust-lang.org/t/psa-tracking-for-gated

坦率地说,我看不到关闭这个的论据......我很高兴现在存在更有针对性的问题,因此实施可以向前推进,希望有新的讨论和重点,但我看不到一种明确的方式来关联编译器与那些消息。

同样,如果这需要(或已经有)关于内部的线程,也许让我们把这个对话移到那里?

编辑:或者只是这本书已经过时了? 尝试 RFC 中的示例(它缺少几个#[derive(...)] s)似乎在 Rust rustc 1.31.1 上没有错误。 是否还有指向这里的编译器错误消息? 有一个地方可以链接错误,例如:

error: only int, `bool` and `char` operations are stable in const fn

如果我们想让它们与可能会改进的具体问题联系起来。

好的,所以这里应该有一些强有力的证据证明这个问题仍然存在。 据我所知:

https://github.com/rust-lang/rust/blob/6ecad338381cc3b8d56e2df22e5971a598eddd6c/src/libsyntax/feature_gate.rs#L194

是唯一指向已关闭问题的active功能。

在一个理想的世界里,我相信这类讨论应该是自动化的,因为正如我们所发现的,人们对事情应该如何运作有不同的意见和想法。 但这真的不是这个线程的对话......

如果我们想让它们与可能会改进的具体问题联系起来。

是的,这是正确的解决方案,也是@Centril已经建议的。

最初的评论也已经过编辑,以将人们重定向到在@ErichDonGubler提到的“窗口”中到达这里的特定问题。

https://github.com/rust-lang/rust/issues/57563现已开放以跟踪剩余的不稳定 const 功能。

那么有人可以在此处编辑问题正文以突出链接到#57563 吗?

@glaebhoerl完成 :)

嗨,我来到这里是因为我在编译 ncurses-rs 时得到了error[E0658]: const fn is unstable (see issue #24111) 。 我该怎么办? 升级生锈? 我有

$ cargo version
cargo 1.27.0
$ rustc --version
rustc 1.27.2

编辑:做brew uninstall rust并遵循rustup install instructions ,现在rustc --versionrustc 1.33.0 (2aa4c46cf 2019-02-28)并且该错误消失了。

是的,为了能够在稳定版上使用const fn ,您需要更新编译器。

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