Rust: RFC 2046 的跟踪问题,标签中断值

创建于 2018-02-27  ·  135评论  ·  资料来源: rust-lang/rust

这是 RFC 2046 (rust-lang/rfcs#2046) 的跟踪问题。

脚步:

未解决的问题:

B-RFC-implemented B-unstable C-tracking-issue F-label_break_value T-lang

最有用的评论

@没有船

我想避免向 Rust 的控制流工具箱添加更广泛适用、高度灵活的选择。 我只对添加针对特定重要用例的控制流感兴趣,并且具有很高的人体工程学影响。

我同意这个原则,我认为这个特性符合那个标准(针对特定的、重要的用例的熟悉的控制流)。 有很多次,当我审查或编写像上面提供的示例一样的代码时,我觉得这是迄今为止编写此代码的最干净和最容易阅读的方式。 重用现有的控制流结构,如loop并在末尾添加无条件的break误导用户,并且由于使用?await!而立即应用的函数通常是不够的

使用loop + 尾随break令人困惑,我们应该更喜欢让用户陈述他们的真实意图,而不是要求他们滥用为不同风格的控制流设计的工具。 这类似于我们甚至一个loop结构,而其他语言则满足于while true { ... } 。 这样做可以编写表达更清晰意图并因此更具可读性的代码。

此外,这个特性是我一直期望Rust 拥有的,因为我们已经标记了大多数其他事物和打破标记的事物,您无法标记或破坏块的事实似乎令人困惑,并且我错了。

TL;DR:我认为这个特性支持现实世界的用例,这些用例只能通过大量嵌套的if语句或滥用其他结构(如循环中断值)来编写。 这是对表面语法的一个小补充,它使块的行为符合我的预期,并允许我编写我的意思的代码,而不是更黑客的东西。

所有135条评论

使用“return”会对标记为? (tryop 问号运算符 thingy)产生有趣的影响。

使用 return 作为关键字而不是 break?

@mark-im 和@joshtriplett已经公开反对返回,但我会加入,因为这显然仍然是一个悬而未决的问题。

中断(和继续)仅可用于循环。
如果你能打破某些东西,你就可以继续它。 (我认为在块上选择 continue 没有明显的语法。)

在 C、C++、Java、C#、JavaScript 和可能更多的语言中,您通常会break使用 switch 语句以防止失败。 Rust 用 | 更好地解决了这个问题在模式中,但来自这些语言的人不会真正将break视为仅用于 for 循环的东西。 特别是当 Java 和 JavaScript 也通过break而不是return公开该功能时。

然而,“要记住的规则”论点在另一个方向上非常有效。 据我所知,这是上述语言以及 Rust 的共性,返回值仅适用于函数而不适用于其他任何语言。 因此,如果您看到返回,您就知道有一个函数被留下了。

标记块不会导致现有未标记中断错误

首先,我认为这种情况很少发生,因为标记中断功能肯定不是每 1000 行使用 10 次的东西。 毕竟,它仅适用于跨越块边界的未标记中断,而不适用于块内的未标记中断。 其次,Rust 的用户习惯于收到编译器的抱怨/错误消息,他们会很乐意修复它们! 第三(这是我认为最重要的一点),如果不是标记一个块,而是将它包装成一个循环,您已经需要注意未标记的中断,并且没有错误消息可以方便地列出中断语句,您有自己去寻找它们:)。

特别是当 Java 和 JavaScript 也通过 break 公开该功能而不返回时。

这对我来说是杀手锏。 打破块是许多语言中的事情。 从块中返回......没有那么多。

就个人而言,我分享@joshtriplett对使用break而不是return看法,但在我看来,RFC 上的讨论还没有得到解决......如果你相信这个问题已在 lang 团队中解决,请在方框中打勾 =)

只是说我正在研究这个。 不需要导师指导。 只是为了不重复任何努力。 期待很快的 PR。

我仍然赞成return不是break ,但我同意在这里不同意。 问题解决了。

目前(使用rustc 1.28.0-nightly (a1d4a9503 2018-05-20) )rustc 不允许在带标签的块上使用unsafe 。 这是预期的吗?

@topecongiro是的,我认为目前仅在普通块上允许这样做是有意的。 将来它可能会改变,但鉴于这是一个如此低级和不寻常的功能,我倾向于将其作为一个功能而不是限制。 (在极端情况下,我当然不想要else 'a: { 。)

肯定同意。 不安全 + 不寻常的控制流听起来像是要劝阻的事情。

不过,在紧要关头,您可以使用:

'a: {
unsafe {...}
}

对?

实际上,虽然其他确实创建了一个新的词法范围,但它不是一个块。 整个 if-else 是一个块(有点)。 所以不,你不会得到else 'a: {你会得到'a: if ... else {

else包含一个块(表达式)。 没有块就没有“新词法范围”。
else更糟糕的表面语法位置是'a: while foo 'b: {...}
(有趣的是, continue 'abreak 'b ,我们可能希望至少在内部依赖它)

(有趣的是, continue 'abreak 'b ,我们可能希望至少在内部依赖它)

这是一个很好的观察!

我认为标签应该是包含块的表达式的一部分,而不是块本身。 我们已经有了loop先例。 (碰巧的是,一个普通的块本身也是一个包含块的表达式。但是像ifloop是包含块的表达式,我猜不是块。)

(像whilefor这样的东西不应该支持 lab​​el-break-value,因为它们可以或不能根据它们是否正常完成或使用break返回值.)

@eddyb

(有趣的是,继续 'a is break 'b,我们可能希望至少在内部依赖它)

仅当break 'b重新检查循环条件时...

@mark-im 等价于'a: while foo {'b: {...}}break不会检查循环条件,循环本身会,因为在 body 块的每次迭代之前都会检查循环条件。

哇,我觉得_非常_不直观。 我希望break 'b基本上是goto 'b ,这意味着我们永远不会退出循环体并且不会再次检查条件......

哦 :man_facepalming: 我明白了...

这就是为什么我不喜欢标记为 break/continue 的原因:/

嗯,我们特别没有能力标记这些奇怪的内部块,所以我没有看到问题。 break总是意味着“离开这个块”,鉴于上述限制,除了“转到相关的右括号之后的位置”之外,没有任何其他意义。

我的困惑并非特定于奇怪的内部块,但我真的不想重新开始讨论。 这已经发生了,社区决定添加它。

好的,我知道可访问性是编程语言的一个大问题……但是,如果您像我一样编写代码,标记 break 将非常有用。

那么,我们如何才能使标记中断更易于访问?

那么,我们如何才能使标记中断更易于访问?

这是一个很好的问题。 我的一些想法:

  • 我们应该收集一些关于人们如何在野外使用它的样本。 我们可以寻找不良模式或懒惰的习惯。
  • 当使用标记的中断/继续被认为是不好的做法时,我们应该有自以为是的风格。
  • 我们也许可以为一些可以机械地变成循环/迭代器组合器的模式添加 lint(不过,我想不出任何这样的模式)。

作为第一个(公认有偏见的)样本,我最后一次(也是第一次)遇到真实代码中的标记中断并不出色: https :

那是……没有标记为break?

关于breakcontinue脱糖的,RFC 的作者在原始 RFC 讨论中也提到了这一点; 见https://github.com/rust-lang/rfcs/pull/2046#issuecomment -312680877

我认为名称break是次优的,但它是为循环而建立的。 更“有原则”的方法是使用语法return 'a value ,该语法在块'a之后立即继续执行,使用该块的值value

@mark-im“不使用功能,因为它不可访问”不是“使所述功能可访问”。

我们如何调整标记的 break 以便我获得语言的全部表达能力,同时您不再抱怨您的大脑无法像编译器一样处理流程控制的事情?

(此外,您链接的代码似乎与 RFC 2046/label-break-value 无关……您是否链接了错误的代码?)

那是……没有标记为break?

(此外,您链接的代码似乎与 RFC 2046/label-break-value 无关……您是否链接了错误的代码?)

确实如此,在我更改它之前,这是一个正常的标记为 continue,但我认为存在相同的问题(并且可能更糟,因为例程其余部分的控制流可能会受到您返回的值的影响)。

@mark-im“不使用功能,因为它不可访问”不是“使所述功能可访问”。

我们如何调整标记的 break 以便我获得语言的全部表达能力,同时您不再抱怨您的大脑无法像编译器一样处理流程控制的事情?

对不起,我没有抱怨的意思。 如果我是唯一有这种感觉的人,我不介意让开。

恕我直言,这是标记为中断/继续的一个基本问题:它太具有表现力了,我知道减轻它的唯一方法是将推荐用法作为“良好风格”(无论这意味着什么)。 例如,“仅在循环体的开头或结尾(而不是中间)使用带标签的 break 值。” 这意味着退出带有值的循环的可能方法很容易被发现和推理。

这就是我在其他语言中避免转到/标记中断的方法: https: //gist.github.com/SoniEx2/fc5d3614614e4e3fe131/#file -special-lua-L4-L72

是不是更易读?

如果是这样,也许我们可以根据标记块找出某种条件系统。 与类似,也许。

未标记的 break 和 continue 的要点是提供那些您无法轻松将条件/值放入循环标题中的情况。 一些循环更直接、可读、快速等,中间有中断。

在循环中标记 break 和 continue 的要点是相似的——有时唯一的选择是引入一个新变量、一个新函数(从而滥用return )或其他一些只会让事情更难理解的扭曲,但是不幸的是,标记中断可能是令人费解的。

但这两个功能甚至不是该线程的内容。 它们相当通用,正是因为它们为表达固有复杂的控制流提供了改进。 这个线程是关于打破非循环块的。 这当然更新颖,人们可能不知道在循环外寻找break ,尽管需要标签意味着一旦您知道它的含义,信号仍然存在。

这就是我对你的例子的意思,@mark-im-它是标记循环的相当标准的使用,而不是标记

更“有原则”的方法是使用语法 return 'a value,它在块 'a 之后立即继续执行,使用该块的 value 值。

关于breakreturn旁注:我更喜欢break因为它是静态控制流。 return是动态的,因为它返回给调用者,调用者可能在任何地方。 这意味着“我已经完成了我的责任,没有其他地方可以看到我所做的事情。” break总是出现在词法范围内的某个地方,就像循环一样。

我认为return 'label expr从“按照我说的做”的角度读起来非常好,因为它可以被认为是“将 expr 返回到位置 'label” 。 我不认为break 'label expr从这个角度读起来同样好......

与其他编程语言隔离,因此我可能会提倡return 'label expr 。 然而,考虑到其他语言的控制流,重用return突然变得不那么可行了,这让我倾向于break 'label expr

我坚信它应该是break 'label expr不是return 'label expr 。 否则将与我们在循环中现有的break 'label用法完全不一致。

@SoniEx2我想我更喜欢你发布的片段,主要是因为变量是记录循环不变量的好方法。 OTOH,可能可以对标签的名称执行相同的操作(即,任何时候输入这个带标签的块,不变 P 成立)。 我想这是一个可以从野外获得更多代码示例的地方......

稳定提案

该功能由@est31在 https://github.com/rust-lang/rust/pull/50045 中实现,自 2018 年 5 月 16 日(+17 周)以来每晚都在使用,因此它已经足够稳定了。 此外,自合并实施此功能的 PR 以来,没有报告任何问题。 从那以后,所有未解决的问题也都得到了解决,并且一致认为break应该是表面语法而不是return

因此,我开始稳定标签中断值(RFC 2046)。

@rfcbot合并

报告

  • 功能门:

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/feature-gates/feature-gate-label_break_value.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/feature-gates/feature-gate-label_break_value.stderr

  • 诊断:https://github.com/rust-lang/rust/blob/master/src/librustc_passes/diagnostics.rs#L285
  • 测试:

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_continue.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_unlabeled_break.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_illegal_uses.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/lint/unused_labels.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/run-pass/for-loop-while/label_break_value.rs

FCP之前的TODO

@rfcbot关注 FIXME-in-tests

最后一个测试文件当前有一个 FIXME:

// FIXME: ensure that labeled blocks work if produced by macros and in match arms

这应该在稳定之前解决。
我写了一些测试来检查预期的行为。 FIXME 实际实现了:

// run-pass

#![feature(label_break_value)]

#[test]
fn lbv_match_test() {
    fn test(c: u8, xe: u8, ye: i8) {
        let mut x = 0;
        let y = 'a: {
            match c {
                0 => break 'a 0,
                v if { if v % 2 == 0 { break 'a 1; }; v % 3 == 0 } => { x += 1; },
                v if { 'b: { break 'b v == 5; } } => { x = 41; },
                _ => {
                    'b: {
                        break 'b ();
                    }
                },
            }
            x += 1;
            -1
        };

        assert_eq!(x, xe);
        assert_eq!(y, ye);
    }

    test(0, 0, 0);
    test(1, 1, -1);
    test(2, 0, 1);
    test(3, 2, -1);
    test(5, 42, -1);
    test(7, 1, -1);
}

#[test]
fn lbv_macro_test() {
    macro_rules! mac1 {
        ($target:lifetime, $val:expr) => {
            break $target $val;
        };
    }
    let x: u8 = 'a: {
        'b: {
            mac1!('b, 1);
        };
        0
    };
    assert_eq!(x, 0);
    let x: u8 = 'a: {
        'b: {
            if true {
                mac1!('a, 1);
            }
        };
        0
    };
    assert_eq!(x, 1);
}
// compile-fail

#![feature(label_break_value)]

fn lbv_macro_test_hygiene_respected() {
    macro_rules! mac2 {
        ($val:expr) => {
            break 'a $val;
        };
    }
    let x: u8 = 'a: {
        'b: {
            if true {
                mac2!(2);
            }
        };
        0
    };
    assert_eq!(x, 2);

    macro_rules! mac3 {
        ($val:expr) => {
            'a: {
                $val
            }
        };
    }
    let x: u8 = mac3!('b: {
        if true {
            break 'a 3;
        }
        0
    });
    assert_eq!(x, 3);
    let x: u8 = mac3!(break 'a 4);
    assert_eq!(x, 4);
}

在我们从提议的 FCP 转移到 FCP 之前,应该添加与这些类似的测试。

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

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

关注点:

  • 成本效益(https://github.com/rust-lang/rust/issues/48594#issuecomment-422235234)
  • FIXME-in-tests (https://github.com/rust-lang/rust/issues/48594#issuecomment-421625182)
  • 用例(https://github.com/rust-lang/rust/issues/48594#issuecomment-422281176)

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

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

@rfcbot关注成本效益

来自RFC FCP 提案

另一组认为此功能对语言的“成本效益”并没有完全削减。 换句话说,该功能会带来更高的复杂性——换句话说,人们可能会实际使用它并且你必须阅读他们的代码,我想,以及语言的整体规模——与其实用性不相称。

我仍然认为我们根本不应该拥有这个功能。 自从实施以来,它是否被大量使用? 在已经使用的情况下,使用函数是否明显更糟? 如果这里有好处,它是否会超过使语言变得更大更复杂的成本?

@nrc我也有同样的担忧。 我确实理解让它可用以便宏可以使用它的论点,但与此同时,我宁愿根本没有它。

我不想从原始 RFC 线程中重读参数,但我确实认为这是询问有关此功能的经验的合理点。 它看到了什么用途?

手写解析器,主要是......(我喜欢我的手写解析器>.<)

与标记传播( try_foo() 'bar? )一起使用会更有用/更容易。

@rfcbot关注用例

总结关于 Discord 的一些讨论:我们希望从实际代码中看到具体的用例,当重写为不使用此功能时,不会更清楚地看到这些用例。

〜FWIW,我实现EXPR is PAT依靠break 'label value是可至少在AST,我不知道脱糖如何能没有它的工作。
EXPR?在编译器中的实现也依赖于break 'label value ~

~它是更大的控制流功能的某种基本构建块,因此无论如何可能需要在编译器中实现。
所以这里的“成本”可能只是让它在表面语法中可用。~

编辑:我完全误解了这个问题,我虽然这是关于loop { ... break 'label value ... } ,而不是阻止{ ... break 'label value ... }
我从来没有机会尝试这个,因为我总是忘记它已经实施了。

@petrochenkov谈起上龃龉@joshtriplett,他们指出,他们担心复杂的面向用户的,而不是语言的实现。

我认为复杂性的增加是最小的:对于读者来说,这意味着什么应该很明显,因为这个概念已经存在于循环等中。
否则,我必须使用带有无条件 break 语句的循环,这不太清楚,事实上,甚至还有一个关于此的剪贴画 (never_loop)。 所以我认为这是有好处的。

至于用例,该主题已经出现在 RFC 中。 我在这里指出了我的用例。 另请参阅下面直接由@scottmcm列出的用例。 也许线程中有更多内容,idk。 @joshtriplett是否解决了用例问题?

我同意@nrc@joshtriplett 的观点,我还想在这里提出一个流程问题:我们暂时接受了这个 RFC,但明确警告说,在重新审视@nrc@joshtriplett提出的问题时,稳定性被阻止了,但@Centril的合并提案根本没有提到这个阻塞问题,并将其视为非常标准的“功能已经烘焙”合并。 我不是为此责怪@Centril ,而是流程崩溃:如果我们要暂时接受具有未解决的

就我们的整个流程而言,我很担心这两天半没有提出阻塞问题,并且大多数团队成员已经检查了他们的框。 可以想象,由于我们不再需要所有成员的积极共识,这甚至可以在没有提出阻止者的情况下进入 FCP。 这感觉像是对先前协议的颠覆,导致我同意合并 RFC,我认为这完全是由于信息跟踪不足造成的。

@withoutboats是的,没错。 这让我在未来不太倾向于在“我们将在稳定过程中进行 XYZ”的基础上接受事物,直到我们有一些适当的过程,使得它极不可能被遗漏。

@没有船

我不是为此责怪@Centril ,而是流程崩溃:如果我们要暂时接受具有未解决的

不过还是要道歉; 我不知道这个警告,但我应该在创建跟踪问题时检查这些事情。

这感觉像是对先前协议的颠覆,导致我同意合并 RFC,我认为这完全是由于信息跟踪不足造成的。

我应该为它提出一个未解决的问题; 我相信过程错误发生在那个时候。
至于如何改进流程; 我认为将未解决的问题写入 RFC 的_text_很重要; 否则很难找到它们;)


至于 fcp-merge 提案; 我个人确实认为出于一致性和宏使用的原因,它会很有用。 但是,如果您认为现在提出稳定措施还为时过早; 随时取消提案:)

@est31

否则我必须使用带有无条件 break 语句的循环,

或者重构代码以避免两者之一。

在这里指出了我的用例。

为什么不用return Ok(vectors);替换break 'pseudo_return return Ok(vectors);

正如我在这里提到的,这对手写解析器很有用(即使没有标记传播( try_foo() 'bar? ))。

label-break-value 允许其他功能代码的简单命令化。 通常,命令式代码比函数式代码更具可读性。

或者重构代码以避免两者之一。

当然,Rust 是图灵完备的。 但重组可能有点困难。 总体而言,您可以基于“您可以只使用现有方式”来拒绝几乎所有的糖功能(这就是糖功能)。

为什么不用 return Ok(vectors); 替换 break 'pseudo_return;?

实际上,在这种情况下,您是对的,可以用返回 Ok 替换中断。 但在其他情况下,您可能希望在之后进行处理,等等。借用检查器在跨函数边界时效果不佳,您无法将每个这样的块都分解为一个函数。

无论如何,我已经打破了通过官方方式评论语言功能的沉默,而且我很后悔。 所有相同的点,一遍又一遍地重述。 这狗屎是浪费我的时间,对不起。 所以不要指望我有任何进一步的评论。

@est31我真的很感谢你提供细节; 谢谢。

由于夜间 Rust 的要求,测试和使用这些东西存在可访问性问题。

我的目标是稳定。 我希望有一天能解决这个问题。

如果我们想要用例,这是我不久前遇到的一个; 基本上是对@est31的“事后处理”的详细阐述:我编写了一个词法分析器来处理 C++ 字符串字面量前缀(在实际的 C++ 情况下,{ L , u8的组合爆炸, uU 、 }{ R 、 }) 和多字节标记,这些标记在使用的字节数上有“差距”(不确定一个没有意义的这个例子)。 get_token函数目前看起来像这样:

fn get_token(&mut self) -> Token {
    match decode_byte(self.source) {
        // ...

        // repeat four times with small variations for b'U', b'L', and b'R':
        Some((b'u', rest)) => match decode_byte(rest) {
            Some((b'"', rest)) => self.string_literal(Utf16String, rest),
            Some((b'\'', rest)) => self.char_constant(Utf16Char, rest),
            Some((b'R', rest)) => match decode_byte(rest) {
                Some((b'"', rest)) => self.raw_string_literal(Utf16String, rest),
                _ => self.identifier(rest),
            },
            Some((b'8', rest)) => match decode_byte(rest) {
                Some((b'"', rest)) => self.string_literal(Utf8String, rest),
                Some((b'\'', rest)) => self.char_constant(Utf8Char, rest),
                Some((b'R', rest)) => match decode_byte(rest) {
                    Some((b'"', rest)) => self.raw_string_literal(Utf8String, rest),
                    _ => self.identifier(rest),
                },
                _ => self.identifier(rest),
            },
            _ => self.identifier(rest),
        },

        // ...

        // the "gap" mentioned above is here: single-byte '.' and triple-byte '...' but no double-byte '..':
        Some((b'.', rest)) => match decode_byte(rest) {
            Some((b'0'..=b'9', rest)) => self.number(rest),
            // note the _inner to avoid shadowing the outer `rest` used by the inner `Dot` case:
            Some((b'.', rest_inner)) => match decode_byte(rest_inner) {
                Some((b'.', rest)) => self.make_token(Ellipsis, rest),
                _ => self.make_token(Dot, rest),
            },
            _ => self.make_token(Dot, rest),
        },

        // ...
    }
}

注意_ => self.identifier(rest)的金字塔(对uURL重复四次)和_ => self.make_token(Dot, rest) s,形成一种延续传递风格,其中identifierstring_literal等都必须调用make_token

我本来希望使用break -from-block 将事情整合回不那么连续的风格,并且几乎是通过标记loop的,但认为该版本太奇怪而无法阅读。 更具体:

  • 我会将所有make_token调用移动到主match decode_byte(self.source)之后的单个位置,并将其内联 - 它很小并且包含unsafe其不变量由get_token
  • 一旦找到"' ,我会使用break 'label self.string_literal(..)短路,然后将所有self.identifier(..)调用组合到该匹配臂的末尾.

    • 我可能也能够线性化前缀的组合爆炸 - 检查u / u8 / U / L然后检查R 。 这使用较少的break 'label s,但仍然很少。

  • 我会用break 'label (Ellipsis, rest)短路一旦找到一个... ,然后组合这两个(Dot, rest) s至匹配臂的端部。

总的来说,这基本上是“使用 if + early return 扁平化控制流”,不需要将事物提取到单独的函数中。 在这种情况下,这非常有价值,原因如下:

  • 这些函数中的大多数都是很小的、没有好名字的一次性函数,只会让事情变得比这种 continuation-y 风格更不可读。
  • 其中一些函数需要一堆额外的参数,否则这些参数只是具有推断类型的直接局部变量。

    • 或者,关闭,在其中一些情况下会导致借用检查器问题。

  • 正如我上面提到的,这里的函数之间存在安全不变量。 这里的代码越直线,状态机越好,尤其是当人们稍后回来对新的令牌类型进行小的更改时。

我会写出整件事,但我想我从未尝试过(可能是因为我遇到了上面列出的所有问题)并且我已经在这里花费了足够多的话。 :)

@SergioBenitez您能否详细说明http://rocket.rs/label_break_value以及您对稳定性的看法?

@Centril当然! 这是要点:

fn transform(request: &Request, data: Data) -> Transform<Outcome<_, _>> {
    let outcome = 'o: {
        if !request.content_type().map_or(false, |ct| ct.is_form()) {
            break 'o Forward(data);
        }

        let mut form_string = String::with_capacity(min(4096, LIMIT) as usize);
        if let Err(e) = data.read_to_string(&mut form_string) {
            break 'o Failure(FormDataError::Io(e));
        }

        Success(form_string)
    };

    Transform::Borrowed(outcome)
}

使用此功能,我避免:

  • 将块拆分为不同的功能,以便提前“返回”。
  • Transform::Borrowed到块中的每个“返回”值。
  • 如果在不同情况下返回不同的Transform则可能不正确。

    • 注意:这是 Rocket 和这段代码特有的不变量。

我很高兴看到这存在。 这正是我想写的。 话虽如此,我可以清楚地以不同的方式编写它,而不依赖于此功能。

@SergioBenitez谢谢! 我想知道你是否可以(最终)用try { .. }重写它? 像这样:

fn transform(request: &Request, data: Data) -> Transform<Outcome<_, _>> {
    Transform::Borrowed(try {
        if !request.content_type().map_or(false, |ct| ct.is_form()) {
            Forward(data)?;
        }

        let mut form_string = String::with_capacity(min(4096, LIMIT) as usize);
        if let Err(e) = data.read_to_string(&mut form_string) {
            Failure(FormDataError::Io(e))?;
        }

        form_string
    })
}

@Centril是的,只要只有一条成功路径,这就会起作用,这里就是这种情况。

或者重构代码以避免两者之一。

通过这样做,您将面临添加优化器无法解析的额外分支或子例程调用的风险。 对我来说,这样的控制流转换似乎是一项相当复杂的任务。

在已经使用的情况下,使用函数是否明显更糟? 如果这里有好处,它是否会超过使语言变得更大更复杂的成本?

一个小函数可能会被内联,但无论如何,你会获得不必要的代码膨胀,或者说,愚蠢的代码重复。

我更喜欢最优雅的二进制代码。 您可以通过更高级的控制流来实现,这与从函数中提前返回几乎相同。

我想知道你是否可以(最终)用 try { .. } 重写这个? 像这样:
[...]

这看起来有点令人困惑,因为引入了分支,只是为了优化掉。 但在某些情况下,可能两者都需要。 因此,有

'a: {try{...}}

或者

'a: try {...}

会好的。

我们希望从实际代码中看到具体的用例,当重写为不使用此功能时,不会更清楚地看到这些用例。

请随意羞辱我:

它可能不是最好的代码,但我希望一切都能正常工作。

我想在连续传递风格方面实验性地将主循环重新表述为状态机。 但是延续和强制尾调用是另一个话题。

很高兴看到此功能稳定下来。 我有很多用例,在这些用例中,这种语法显着地简化和阐明了意图。 对于涉及执行易错操作和展开其结果的长部分,这种语法非常棒。

我同意@zesterer 的观点,即此功能的稳定性将有利于生态系统,并使某些模式编写起来不那么烦人:+1:

FWIW,我最近第一次在编译器中使用它。
该模式与前面的示例相同 - 我们正在检查多个条件并在其中任何一个为真时停止执行我们正在执行的操作。
https://github.com/rust-lang/rust/blob/21f26849506c141a6760532ca5bdfd8345247fdb/src/librustc_resolve/macros.rs#L955 -L987

@erickt也出于同样的原因编写了一些想要使用它的代码:检查多个条件,一旦其中一个变为 false 就中断。 你可以用立即调用的闭包或let _: Option<()> = try { ... None?; .. };来伪造它,但两者都非常棘手。

@withoutboats @nrc @joshtriplett你对迄今为止各种人提出的用例有什么想法吗?

Ping @withoutboats @nrc @joshtriplett :) -- 上次 lang 会议我们讨论了你们可能会尝试重写上面给出的一些示例并向我们展示如何构建它们的可能性。

我觉得受到了Greydon 的博客文章

我的总体反对意见是,我只是认为 Rust 不需要更多开放式分支控制流结构。 我们已经有了匹配和循环,然后在这些代数数据类型、语法糖、函数抽象和宏之上,创建了一个庞大的控制流模式数组,供任何普通用户处理。 这已经让人觉得它变得势不可挡,我个人不得不采用一些规则来管理决策树的复杂性(例如,我有一个规则,永远不要将组合子作为第一遍:只有在它之后很明显时事实上,作为组合器会更好)。

我想避免向 Rust 的控制流工具箱添加更广泛适用、高度灵活的选择。 我只对添加针对特定重要用例的控制流感兴趣,并且具有很高的人体工程学影响。 在我看来,这种结构具有完全颠倒的属性:它广泛适用,极具可塑性,并且仅比其他替代方案稍微方便一些。

此外,我认为此功能还有另一个非常负面的品质:由于向后兼容导致的不规则性。 当break脱离未标记的循环时,仅当它们被标记时才脱离块是非常不规则的。 这使得该功能比常规功能更难理解,在我认为最糟糕的地方加剧了负面属性 - 可理解性。

我还认为有很多更重要的特性需要关注,而且由于我们在这方面存在明显分歧,我宁愿推迟对它的任何考虑,也不愿尝试就该提案进行长期的协商一致过程。

我认为正确的做法是找到每个使用此功能的 crate 并重写代码以便不使用此功能。

@withoutboats感谢您非常有效地表达了我也持有的许多相同的反对意见。

我要继续大胆地在这里:

@rfcbot取消
@rfcbot推迟

@joshtriplett提议被取消。

团队成员@joshtriplett已提议推迟此操作。 下一步是由其他标记团队进行审查:

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

当前未列出任何问题。

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

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

@没有船

我想避免向 Rust 的控制流工具箱添加更广泛适用、高度灵活的选择。 我只对添加针对特定重要用例的控制流感兴趣,并且具有很高的人体工程学影响。

我同意这个原则,我认为这个特性符合那个标准(针对特定的、重要的用例的熟悉的控制流)。 有很多次,当我审查或编写像上面提供的示例一样的代码时,我觉得这是迄今为止编写此代码的最干净和最容易阅读的方式。 重用现有的控制流结构,如loop并在末尾添加无条件的break误导用户,并且由于使用?await!而立即应用的函数通常是不够的

使用loop + 尾随break令人困惑,我们应该更喜欢让用户陈述他们的真实意图,而不是要求他们滥用为不同风格的控制流设计的工具。 这类似于我们甚至一个loop结构,而其他语言则满足于while true { ... } 。 这样做可以编写表达更清晰意图并因此更具可读性的代码。

此外,这个特性是我一直期望Rust 拥有的,因为我们已经标记了大多数其他事物和打破标记的事物,您无法标记或破坏块的事实似乎令人困惑,并且我错了。

TL;DR:我认为这个特性支持现实世界的用例,这些用例只能通过大量嵌套的if语句或滥用其他结构(如循环中断值)来编写。 这是对表面语法的一个小补充,它使块的行为符合我的预期,并允许我编写我的意思的代码,而不是更黑客的东西。

@cramertj对不起,我有点困惑。 听起来你和@withoutboats / @joshtriplett说的完全相反?

(fwiw,我同意@withoutboats /@joshtriplett)

@mark-im 我不同意他们应该关闭此功能。 我认为它应该合并(如我的评论和上面标记的复选框所示)。

我同意@withoutboats 的观点,即我们不应该添加不受特定重要用例驱动的新工具。 我认为这个功能让人感觉很熟悉,并且是由特定的、重要的用例激发的。

@没有船

我觉得我同意这个实验有点不公平; 很难证明是否定的,但我不知道什么样的代码示例可以克服我对将这种形式的控制流添加到语言的反对。

我不认为我们设置的标准是“向我们表明不存在可以用 label-break-value 更好地编写的代码”——而是“向我们展示我们想要 label-break-value 的特定代码可能是用另一种方式写得更清楚。” 当您和其他人要求提供有关此功能在哪里有用的激励示例时,此对话就开始了,并且该线程上的许多人(包括我自己)都提供了示例。 他们没有说服你或@joshtriplett ,所以我现在问你们如何在没有 label-break-value 的情况下编写这些示例。 您是否同意使用 label-break-value 更好地编写示例? 如果是这样,您是否认为它们的普遍程度不足以超过允许用户编写潜在复杂的中断到块代码的成本?

我要继续大胆地在这里:

@rfcbot取消

@scottmcm提议被取消。

只有当它们被标记时,break 才从块中脱离出来,当它脱离未标记的循环时,这是非常不规则的。

不确定它是否在线程中被提及,但是在某种意义上, break;无法定位的块使标记块在质量上比标记为loop的块更强大。

使用带标签的块,您可以制作一个控制流宏,该宏支持其中的break; s,而宏 infra 对用户来说是完全不可见的。
对于带标签的循环,总是存在使用无标签break;的风险,并且会被宏基础架构而不是预期目标捕获。

在我的EXPR is PAT原型中,我最初使用通常的loop进行脱糖,但是由于上述问题,我无法引导编译器。
那时还没有实现带标签的块,所以我不得不在 AST 中引入一个特殊的“不可定位的loop ”并在脱糖中使用它。

正如我的复选框所示,我现在赞成稳定这一点。 我认为这是语言的自然扩展,它促进了宏以及不太容易适应功能模式的控制流。 即使 NLL 使生命周期变得非词汇,我认为能够用生命周期注释块也让我觉得在教学上很有帮助,就像类型归属一样。

然而,由于共识很难达成,为了找到它,我建议我们尝试加快try { ... } ,并加快https://github.com 的试验工作@petrochenkovEXPR is PAT语法同时使用)。

抱歉,现在情况如何? 我们是否在 FCP 中?

@mark-im 由于没有任何标签proposed-final-comment-periodfinal-comment-period是关于这个问题的,我们不在 FCP 或提案中。

(我们从未进入过 FCP,尽管最初提议进入 FCP 时只勾选了三个复选框)

@scottmcm

我要继续大胆地在这里:

重复@joshtriplett的措辞,我读起来非常讽刺。 作为 lang 团队的一员,请考虑如何与其他贡献者交流。

我确信这实际上是一个好主意。 之前我一直很反感这个功能,我仍然不认为这是一个程序员应该使用的功能。 上面的用例有些说服力,但我认为它们仍然可以更好地分解为更小的函数。 我发现真正有说服力的案例是宏的输出。 如果用户可以return某种方式插入goto 。 我想知道“大”解决方案是否是可以输出 HIR 而不是令牌的宏,但这有点过时了。 同时,为宏作者提供这个功能会很好,所以总的来说我认为我们应该稳定下来。

有没有人试过以一种更慢、更不直观、更不符合人体工程学的方式重写这些东西?

打破积木就像符合人体工程学的转到。 它几乎没有 goto 的任何问题,但同样强大。

基于@nrc的改变,我再次移动以稳定label_break_value
我的报告可以在https://github.com/rust-lang/rust/issues/48594#issuecomment -421625182 中找到。

@rfcbot合并
@rfcbot关注 FIXME-in-tests

为确保@joshtriplett@withoutboats有时间提出他们可能仍然存在的任何问题,我将记录此类问题。 一旦他们告诉我他们没有这样的担忧,或者当他们中的一个人提出了他们自己的担忧时,我就会解除它。

@rfcbot关注给船和 josh-time-to-

作为此问题和所有其他问题的流程说明,请避免来回取消提案……如果您认为某些事情不应该向前推进,请使用疑虑。

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

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

关注点:

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

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

@rfcbot关注阻塞

我已经表达了我的立场,就是Rust不应该有这个功能。 我怀疑这对于该项目来说是否具有足够高的优先级,以至于我可以在不久的将来的任何时候花时间就该提案达成共识。

@rfcbot解决给船

如果这很重要: Zig 也具有此功能

在2019年1月5日上午9点18分29秒的PST,马萨达克Farrokhzad [email protected]写道:

@rfcbot解决
给船和乔希时间提出任何问题,他们可能仍然有

我们已经提出了担忧,它们相当于“这不应该出现在语言中”。 这些担忧不会消失。

我更喜欢 nrc 的观点,即宏应该能够生成 IR。 对于这种情况和许多未来的可能性,这似乎是一个完全合理的解决方案,而不必增加语言的表面语法。

@joshtriplett

我们已经提出了担忧,它们相当于“这不应该出现在语言中”。 这些担忧不会消失。

明确地说,我的意思是用@rfcbot注册它们,因为在提案被取消后,机器人不会看到注册的旧问题。 我提出这个问题是因为我知道你有顾虑,这是对你的礼貌。

我更喜欢 nrc 的观点,即宏应该能够生成 IR。 对于这种情况和许多未来的可能性,这似乎是一个完全合理的解决方案,而不必增加语言的表面语法。

我不知道那会是什么样子,或者这是一个更好的主意; 它似乎相当投机(从“这可能需要数年才能同意设计”的意义上说)并且比break 'label expr的相当低成本的解决方案成本更高,后者的好处也不仅仅是宏观的结果扩张。

_作为此问题和所有其他问题的流程说明,请避免来回取消提案...如果您认为某些事情不应该向前推进,请使用顾虑。_

我当然同意这一点。 但是,我认为这里的适当机制不是问题。 一个问题似乎是“如果这个问题得到解决,那么我会批准”。 在这里,问题是“这根本不应该进行,‘合并’是错误的目标,我想改为‘关闭’”。 这似乎不是“关注”机制的最佳处理方式。

@joshtriplett作为一个兴趣点,您能否将我引向您之前提到的问题? 我已经浏览了原始的 RFC 线程,但不确定具体指的是什么。 谢谢。

@rfcbot关注人体工程学和优化/性能(降低性能和人体工程学的非锈示例 https://gist.github.com/SoniEx2/fc5d3614614e4e3fe131#file-special-lua )

idk 如果我使用这个权利

(注意一些“脱糖”(实际上我会说它与脱糖完全相反 - goto 是一个原始,将脱糖循环到 goto)非常糟糕)

(还要注意,lua 没有标记任何东西,所以它迫使你使用 goto。即便如此,我相信至少仍然需要一个中断块,但可能需要更清洁的“脱糖”。)

@rfcbot关注 should-close-not-merge

(正如@centril在 Discord 上所建议的

我不确定这是赞成还是反对稳定的论点,但请注意,今天在稳定的 Rust 中编码非常容易:

fn main() {
    'foo: for _ in 0..1 {
        println!("break");
        break 'foo;
        println!("broken");
    }
}

所以一方面,我们应该稳定它,因为它几乎没有增加语言,只是省略for _ in 0..1 ,另一方面我们不应该稳定它,因为今天有一种简单的方法可以做到(当确实有必要),我们不应该鼓励使用这种反模式。

这似乎有点令人困惑,标签是不必要的。

打破块不那么令人困惑,并且需要标签。

我不明白为什么你认为打破块是一种反模式。 适用于 X 的内容不一定适用于不完全 X。 我也许可以花 299.99 美元买一台电脑,但不是 299.98 美元,即使它们“基本上”是一样的。

@nrc

我不确定这是赞成还是反对稳定的论点,但请注意,今天在稳定的 Rust 中编码非常容易:

    'foo: for _ in 0..1 {
        break 'foo;
    }

所以一方面,我们应该稳定它,因为它几乎没有增加语言,只是省略for _ in 0..1

如果明确的目标是不让任何人写'a: { ... break 'a e; ... } ,则'a: for _ in 0..1 { break 'a }可能会起作用; 然而,它的缺点是会生成类型检查器、MIR 和 LLVM 必须处理的垃圾 IR,从而缩短编译时间(至少与 LBV 相比)。

另一方面,我们不应该稳定它,因为今天有一种简单的方法可以做到(在真正需要时),我们不应该鼓励使用这种反模式。

我认为我们不同意 LBV 是一种反模式。 最终,我们选择为 Rust 注入命令式控制流,而不仅仅是让 Rust 成为一种函数式编程语言。 虽然我可能更喜欢没有这样的控制流,但这是既成事实。 那么问题是 LBV 是否比 Rust 中的其他命令式机制更难读或更成问题。 我不认为是。

虽然我认为函数应该保持短(垂直)和浅(缩进),但长而浅总比长而深好。 我发现 LBV 有助于避免一些更复杂的流程中的深度。 LBV 对我来说似乎也是可读的,因为它明确表示它将跳转到的位置,使流程很好理解。 总而言之,我发现 LBV 是问题最少的命令式控制流之一。

  1. 'a: for _ in 0..1 { break 'a }
  2. 'a: { ... break 'a e; ... }

这些可能看起来相似,但它们并不完全相同。 虽然第一个可以说是一种反模式,但第二个可以说不是。

有点像loop {}while true {} (授予loop可以返回一个值而while不能,但让我们暂时忽略它)

@nrc但行为并不完全相同: https :

特别是break;continue;是不同的。 考虑这个假设的代码:

some_macro! {
   ...
   break;
   ...
}

如果some_macro扩展为'a { ... } ,则与扩展为'a: for _ 0..1 { ... }行为不同

@SoniEx2

有没有人试过以一种更慢、更不直观、更不符合人体工程学的方式重写这些东西?

尝试以更积极和更有成效的方式传达您的担忧。 嘲笑别人不会让我们有任何进展,还会让人感觉很糟糕。 我们来到这里是因为我们想确保 Rust 是最好的编程语言,而这个过程的一部分是确保社区尽可能积极和鼓舞人心。

也许我犯了一些大误解。 我不会假装自己是 LLVM、Rust 内部结构或类似方面的专家。 也就是说,我确实对编译器设计有一些基本的经验,而且我对所关心的问题感到困惑。 如果有人可以解释事情,我会很感激。

我看待事物的方式:

1) 这不会改变流量控制的人体工程学。 在 Rust 中已经存在这样的结构,例如'a: loop { if x { break 'a; } break; }

2)这并没有特别改变块返回的人体工程学:循环已经能够返回值。

对我来说,这似乎更像是 Rust 已经拥有的一组功能的直观完成——将这些功能推广到所有(或至少更多)块。

在担心这与goto过于相似时,我更加困惑。 这不会添加任何在 Rust 中不可能的东西,并且它不允许向后跳转(导致goto使用不当的主要问题回归到意大利面条代码),所以我失败了了解此功能在 codegen 中可能产生的后果,因为这似乎实际上只是用于始终中断循环的语法糖。

有人可以更具体地向我解释存在哪些问题吗?

从程序上讲,IMO 不适合提交concern blockingconcern should-close-not-merge样式的问题:此问题未列出所提议的功能或我们可以努力解决的任何问题- - 这只是一个永久阻滞剂。 我不认为 rfcbot 的关注机制应该用于永久阻止功能,而只是为了帮助跟踪关注的解决方案并确保它们得到适当的解决/解决。 我对这个过程的理解是,目的是使用一个未选中的复选框来标记你的分歧,并且将关注点用于提交有关该特性及其功能的特定的、可讨论的问题。

我本可以对我个人不同意的其他功能提出类似的“我不喜欢这个”问题(参见例如uniform_paths),但我不相信无限阻挠是进行语言设计的有效方式。

如果对此功能与应该讨论/解决的语言的其他部分交互的方式有特定的担忧(例如“这似乎可以通过try { ... }来消除”或“它有助于启用非-惯用的大函数”)我认为以这种方式归档它们会更有效率。

@cramertj如果@Centril没有明确建议,我就不会以这种形式提出这样的问题。 (如果没有提出 FCP,我个人会更喜欢。)

怎么建议为“这应该是封闭的”,如果有人已经提交了P-FCP与rfcbot比“亲密”以外的其它适当的过程? “确保它们得到适当的处理/解决”听起来相当于“总有一天会稳定下来,需要什么才能到达那里?”。 这不会在流程中留下“这永远不应该稳定下来,这不应该成为语言的一部分”的路径。

我对这个过程的理解是,目的是使用一个未选中的复选框来标记你的分歧

然后我们需要将流程改回要求选中所有复选框才能继续。

我不相信无限长篇大论是进行语言设计的有效方法。

我也不相信。 我也想找到一条路来结束这个,我只是希望看到它以不同的方向结束。

作为记录,根据此 RFC 的经验,我认为再次回复 RFC 并警告说“我们可以在稳定期间评估/解决我们是否应该继续”不是一个好主意,因为这对我来说似乎很清楚这样做会产生像这样的关键程序问题。

我们需要一条路径来说“是的,我可以看到这个单独的特性如何使语言更具表现力,但基于对语言的整体评估,它没有它的分量,我不想进一步扩展以这种方式语言的表面积”。 我相信我们需要定期发出这样的呼吁,以免每个提议的功能都以某种形式被视为不可避免的,而只是平息反对和坚持的问题。

我将重复我之前说过的,因为它似乎被遗漏了: https :

我基本上谈到了使用循环和使用中断块之间的区别。

主要是,break 块有不同的语法,(稍微)不同的语义(至少相对于未标记的 break),它们表示不同的意图,以及其他一些小事。

rfcbot 支持反关注吗?

@joshtriplett

我们需要一条路径来说“是的,我可以看到这个单独的特性如何使语言更具表现力,但基于对语言的整体评估,它没有它的分量,我不想进一步扩展以这种方式语言的表面积”。 我相信我们需要定期发出这样的呼吁,以免每个提议的功能都以某种形式被视为不可避免的,而只是平息反对和坚持的问题。

是的,这是我们当前流程的一个不幸风险,即接受或拒绝功能需要适当团队的完全共识。 当 lang 团队像现在这样大时,很难总是实现这一点——我从上面的评论中认为,这只是缺少代码示例来激发为什么这个功能很重要,但现在听起来你同意有是使用此功能可以更好地编写的代码,但不相信您在此处看到的成本值得。 我不确定有什么方法可以让您相信这些案例是足够的动机,因为似乎继续提供示例(包括宏示例,这实际上不可能以另一种风格编写)是不够的。

同样,我相当有信心,我个人仍然相信应该合并此功能:IMO 不仅通过各种示例来证明它是最佳/唯一选项,而且语言实际上通过添加更简单(因为不允许标记块是令我感到诧异因为我们允许其他标记结构)。

如果你同意我上面对你的立场的总结,那么我们似乎陷入了一个缺乏流程的不幸(我相信,历史性的!)僵局。 一种选择是像我上面所做的那样解释当前的 rfcbot 规则,意思是“没有复选框表示您的不同意见,需要三个成员来否决多数”,但我不认为这就是引入规则时的含义,所以我认为我建议你应该遵循这个程序是不诚实的(尽管我在其他地方自己做了)。

我之前听说过一些建议,我们可以为从批准->实施->稳定的功能引入时间限制,并且我们应该“自动关闭”落后的功能以避免不断增加的积压。 类似的事情可以解决这种情况,我(以及我认为团队中的其他几个人)不会将复选框标记为关闭,也不会将复选框标记为接受(我什至现在仍然感到害怕,即使这样说我”我放弃了最后的努力来说服你!:smile:)。

我担心随着团队的不断壮大,我们将失去就功能达成共识的能力,并且很难以连贯的方式引导语言,尤其是在 lang-design 团队中。 团队规模限制似乎是解决这个问题的一个明显的解决方案——从很多人那里得到意见是可能的,但在足够大的群体中建立绝对的共识是不可能的。 其他人可能会争辩说,为了保护语言免受错误特征的影响,不应合并有争议的功能。 我个人认为社区不太可能让我们在没有充分警告的情况下犯下许多错误,但这是一个想法。

我将尝试开始一个单独的线程来讨论这里的过程演变,同时我会要求那些在这个线程上发言的人请只在有新的、关键的用例时才发帖考虑这些用例与上面的那些,或者如果有一个重要的未考虑的原因为什么不应该将此功能添加到语言中。 阅读这些大线程上的整个历史很困难,但当帖子一遍又一遍地重复时,或者当线程充满无用的评论时,情况就会变得更加困难。 (他说现在输入了整个线程中最长的评论之一 XD)

TL;DR:我认为我们被卡住了,我们应该为此制定一个流程——我将在别处开始对话并将其链接到这里。 否则,除非您有重要的信息需要考虑,否则请不要发表评论。

@cramertj

这是我们当前流程的一个不幸危险,即接受或拒绝功能需要适当团队的完全共识

老实说,我会认为这是一个功能。

我从上面的评论中认为这只是缺少代码示例来激发为什么此功能很重要,但现在听起来您同意使用此功能可以更好地编写代码,但不相信它值得您在此处看到的费用。

我仍然觉得许多示例可以用其他方式编写。这不会为语言添加任何不可表示的表现力。我最初觉得它可以用足够的例子来激励,但我看到的例子越多,可以使用这个特性,我发现自己越同意@withoutboats这个特性根本不应该进入语言。

(另外值得注意的是: @nrc的箱子proc-macro-rules使用label_break_value似乎已经被重写以避免它。)

一种选择是像我上面所做的那样解释当前的 rfcbot 规则,意思是“没有复选框表示您的不同意见,需要三个成员来否决多数”,但我不认为这就是引入规则时的含义,所以我认为我建议你应该遵循这个程序是不诚实的(尽管我在其他地方自己做了)。

我确实认为这是“弃权”的正确程序,但不是反对。

IMO 它不仅通过各种例子来证明它是最好/唯一的选择,而且语言实际上更简单了它的添加(因为不允许标记块让我感到惊讶,因为我们允许其他标记结构)。

我通常确实发现正交性论点令人信服,但我特别觉得这个论点没有说服力,因为我个人也不希望在语言中标记循环中断。

我担心随着团队的不断壮大,我们将失去就功能达成共识的能力,并且很难以连贯的方式引导语言,尤其是在 lang-design 团队中。

我不仅担心转向,还担心停止。有时会出现某种不可避免的情况,该过程似乎专注于寻找“是”的路径,并且流程图中没有导致“否”,只是“还没有”的图形边缘。

在 2018 年和 2019 年写了很多关于语言特性增长的帖子,我觉得在语言团队中我们需要认真考虑语言的总表面积。我觉得我们没有鼓励我们这样做的良好流程。

@joshtriplett

这不会为语言添加任何不可表示的表现力。

非常清楚:它确实做到了这一点。 目前没有办法表达这种不干扰其他控制流结构的代码,这(正如其他人指出的那样)在宏中尤其可取,这将是唯一通过未标记中断无法定位的结构,允许宏作者在不冒与用户提供的break重叠的情况下中断部分。

它还使代码更具可读性,因为您不必曲折周转(如果我们根本没有标签 - 请参阅 Lua 示例)或使用循环做奇怪的事情。 即使 llvm 应该能够优化 zigzag 代码,这也有一个小的性能优势。

@joshtriplett

我仍然觉得许多示例_可以_用其他方式编写。 这不会为语言添加任何不可表示的表现力。 我最初觉得它可以用足够的例子来激励,但是我看到的例子越多_可以_使用这个特性,我发现自己越同意@withoutboats这个特性根本不应该进入语言。

那是我们可以挖掘的东西吗?

我通常确实发现正交性论点令人信服,但我特别觉得这个论点没有说服力,因为我个人也不希望在语言中标记循环中断。

这条推理线我觉得很奇怪(除非你想用一个版本删除标记的循环中断)。 根据您希望语言中没有的内容来确定设计决策似乎不合适。 它就在那里,所以我们应该考虑这个添加是否与那个一致。 否则,我可能对 Rust 做了很多不同的事情,但我不应该也不能。

我不仅担心转向,还担心_停止_。 有时会出现某种不可避免的情况,该过程似乎专注于寻找“是”的路径,并且流程图中没有导致“否”,只是“还没有”的图形边缘。

还不是它自己的“否”形式,因为如果没有是,它就不会稳定下来。 此外,还有一个不可以:说服我们其他人 LBV 是一个坏主意/出于 X、Y 和 Z 的原因没有足够的动机。 我还没有听到很多这样的具体论点。 您在这里也清楚地证明了没有必然性。

2018 年和 2019 年写了很多关于语言特性增长的帖子,我觉得在语言团队中我们需要_认真_考虑语言的总表面积。 我觉得我们没有鼓励我们这样做的良好流程。

我个人觉得很多这些职位要么是关于可持续发展的(但不是恰如其分地表述为@nikomatsakis没有...)或者说很多人不明白是怎么语言小组的工作(即我们已经到总认真考虑表面积)。 在过去的一年里,Rust 的整体表面语法可能实际上已经缩小了,而不是增长了。 LBV 并没有显着增加,可以提出一个论点,即它实际上减少了语言中的产生式数量并使语法更加统一。

仅仅结合语法产生收缩语言的表面积。

减少一个可以采用的条件分支的数量可以说是缩小了语言的表面积。

我不认为这个特殊功能会以任何方式出现,因此它可能是中性的。 但是,例如,统一语言结构的东西(bool let,有人吗?)确实会缩小表面积。

就其价值而言,Common Lisp 与 Rust 类似,因为它是一种带有宏的多范式语言,具有此功能(名为block / return-from )。 类似于这里讨论的内容,在 Common Lisp 中,这种结构在编写宏和表达不可简化的复杂控制流时通常很有帮助。

(block foo
  (return-from foo "value"))

我的感觉是,在 Common Lisp 中,这个特性被认为是成功的。 它不会出现在关于使语言难以学习或实现的功能的对话中。

有包含

early return from block ⊂ exceptions ⊂ call/cc

例如,在 Scheme 中,以下模拟return-from是可行的:

(define call/cc call-with-current-continuation)

(define-syntax block
    (syntax-rules ()
        ((_ label statements ...)
            (call/cc (lambda (label) (begin statements ...))))))

(block foo
    (display "Visible text")
    (foo "value")
    (display "Phantom text"))

在 Scheme 中, call/cc被认为是成功的,但也是有争议的。 我觉得这特别有趣,因为你首先必须制定一个对象才能谈论它。 即使您认为call/cc是一个错误特征,它也使该语言在知识意义上变得更加充实。

Nemerle语言具有命名块功能。
这是非常有用和方便的。

return、break 和 continue使用此功能作为宏实现。

@jhpratt请阅读问题中的讨论。

@Centril的建议中重复我对 IRLO 的一些评论

在讨论这个特性时,我提出了以下灵感(与通常的循环相关版本相反):

无论如何,循环并不是我在这里的真正灵感。 当我查看 try 块时,我认为“能够编写一些具有早期返回值的简短本地计算而不必费心创建闭包会很有用”。 try 块适用于相当受限的“我想返回一个 impl Try”,并且不支持任何类型的标签。

以上是我的主要用例:我喜欢早退风格。 try {}块让我们完成了大部分工作,但是,同样,它们并不真正支持标签(不是您需要的,因为您将为嵌套块编写try {}? )并制作你将你的类型硬塞进一个特定的单一形状,这不是一个包罗万象的(如提供的用例所证明的那样)。

我还对 C++ 进行了一些观察,其中没有标签中断/继续是非常痛苦的,尤其是在没有类似 Rust 的迭代器的情况下。 例如,如果没有可能的 UB goto或本地break加上内循环外的条件continue ,则无法从内循环继续外循环。

至少 C++ 缺少借用检查器使得内联 lambda 技巧不那么痛苦,这确实有效地支持了 LVB,因为返回内联 lambda 会被 LLVM 的内联程序变成类似 LVB 的东西。 像这样的东西……在 Rust 中更值得怀疑。

我还应该指出,这个特性在表现力上大约相当于 Go 的goto ,编译时强制不跳过声明等(坦率地说,如果 Rob Pike 认为goto是可以接受的,给定试图解决 C++ 问题的历史,我在某种程度上相信他)。

此外,如果我们要进入现有技术,Kotlinreturn<strong i="24">@label</strong> expr;的形式提供了这个确切的功能。

在 c'ish 语言中,即使没有任何传入的 goto,我也经常使用标签作为循环下稳定断点的位置,因为调试器可以识别break function:label
即使没有就中断达成共识,标签也可能很好。

编辑:一个潜在的障碍是标签通常遵循符号命名约定,如果我理解 RFC,这些标签不遵循符号命名约定,其中 ', 在符号名称中无效。
如果实际上这里有任何问题,我不确定例如 Dwarf 或 gdb 本身。

编辑2:
如果我们看一下普通的基于 c 的标签的引用行为,可以肯定这有一些烟雾,
在调试器中,gdb 至少会处理引号,用于引用而不是符号名称的一部分。 结果如下

0x401114 处的断点 1:文件,第 1 行。
无与伦比的报价

echo "void main() { } void hmm() { umm: return; }" | gcc -g -x c -;
gdb -ex "b hmm:'umm'" -ex "b hmm:'umm" -batch ./a.out

而且我不相信这会受到 gdb 中 rust 语言特定支持的影响,因为我相信这种引用发生在符号匹配之前。

编辑:由于现有的循环标签,这艘船可能已经在此航行。

支持从区块中提前返回的一个小点是穷人的合同编程。 一个简单的添加断言语句作为前置和后置条件。 为了保持舒适,应该可以用break 'ret替换return break 'ret以允许这种构造:

let value = 'ret: {
    // ...
};
assert!(postcondition(value));
return value;

然而,这是一个不完美的解决方案,因为return应该被禁止在块内。

添加一个说明我想要这个功能但没有使用它,因为我不知道它存在,我觉得这是对“人们不想要它的负面修饰,因为很少有人使用它”。

今天早上重新定义了这个概念

我今天一直在玩这个,它在async块中_super_ 很方便。 但是,当与try_blocks功能结合使用时,它似乎会遇到问题:

#![feature(try_blocks, label_break_value)]

fn main() {
    let _: Result<(), ()> = try {
        'foo: {
            Err(())?;
            break 'foo;
        }
    };
}
error[E0695]: unlabeled `break` inside of a labeled block
 --> src/main.rs:6:20
  |
6 |             Err(())?;
  |                    ^ `break` statements that would diverge to or through a labeled block need to bear a label

error: aborting due to previous error

try 块是一个错误。

...你不能给?本身贴上标签吗? (如Err(()) 'foo?;

我强烈不同意 try 块是一个错误,尽管这是一个单独的讨论,可能不值得在这里反复讨论。

在这个特定的例子中,它可能是可行的,但与我拥有的真实代码相比,这是非常小的,其中'foo包含大量代码和几个? s。

@SoniEx2

try 块是一个错误。

这个评论是不恰当的。 @jonhoo的评论报告了(大概)有问题的交互。 不管人们对try块(或 label-break-value)有什么看法,很明显它们应该可以顺利互操作。

他们应该使用Err(()) 'foo?;语法。

@jonhoo我怀疑你看到了关于try脱糖方式的 impl 细节泄露——你能把它作为一个单独的问题提交,我们可以把可能的修复的讨论转移到那里吗?

RFC 说

'BLOCK_LABEL: { EXPR }

是语法糖

'BLOCK_LABEL: loop { break { EXPR } }

我尝试进行该替换,并且代码会编译,并带有有关无法访问代码的警告。

#![feature(try_blocks, label_break_value)]

fn main() {
    let _: Result<(), ()> = try {
        'foo: loop {
            break {
                Err(())?;
                break 'foo;
            }
        }
    };
}

@nikomatsakis @ciphergoth提交为https://github.com/rust-lang/rust/issues/72483。

我发现我不再反对这个了。 如果今天要考虑,我会更强烈地反对标记中断的初始概念,但鉴于该概念存在,我认为继续反对将其应用于任意块对我来说没有意义。

(这适用于当前形式,使用break ,不适用于任何其他语法。)

@rfcbot解决 should-close-not-merge
@rfcbot审核

@joshtriplett就其价值而言,我发现这在async块中非常有用,因为这是进行“提前返回”的唯一方法。 这意味着,而不是写:

async {
  // do thing a
  if thing_a_failed {
    // handle specially (note, _not_ ?)
  } else {
    // do thing b
    if thing_b_failed {
      // handle specially (note, _not_ ?)
    } else {
      // do thing c, etc..
    }
  }
}

我可以写:

async {
  'block {
  // do thing a
  if thing_a_failed {
    // handle specially (note, _not_ ?)
    break 'block;
  }

  // do thing b
  if thing_b_failed {
    // handle specially (note, _not_ ?)
    break 'block;
  }

  // do thing c, etc..
  }
}

这是整齐类似于你怎么能早日回用return函数/关闭,并用continue/break在循环中。 诚然,如果我不需要额外的块会很好( async 'block {有可能吗?),但它绝对胜过嵌套的 if-s。

允许使用标签直接注释异步块听起来像是对这个功能的一个很好的扩展。

@rfcbot fcp 取消

我将在这里取消 FCP,因为它已被永久封锁。 我们或许应该讨论是否要推动这个未来。 如果不出意外,似乎应该更新它以将异步块考虑在内,这听起来像是为该功能添加了一个新用例。

@nikomatsakis提议被取消。

请注意,在存在异步块的情况下,该提案的语义没有歧义:RFC 中的定义仍然适用,即

'BLOCK_LABEL: { EXPR }

只是语法糖

'BLOCK_LABEL: loop { break { EXPR } }

除了在 EXPR 内禁止绑定到隐式循环的未标记中断或继续。

请注意,您可以(早期)从异步块中return而不是标记break ,因此标记异步块没有多大意义:

let fut = async {
    return 42;
    0
};

println!("{}", fut.await); // prints 42

操场

@WaffleLapkin我实际上只是来到这里注意到,因为我最近自己async _specifically_ 的适用性小于我最初认为。

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

相关问题

dtolnay picture dtolnay  ·  3评论

zhendongsu picture zhendongsu  ·  3评论

tikue picture tikue  ·  3评论

behnam picture behnam  ·  3评论

pedrohjordao picture pedrohjordao  ·  3评论