Rust: “?”运算符和“ try”块的跟踪问题(RFC 243,“ question_mark”和“ try_blocks”功能)

创建于 2016-02-05  ·  340评论  ·  资料来源: rust-lang/rust

rust-lang / rfcs#243和rust-lang / rfcs#1859的跟踪问题。

实施问题:

  • [x] ?运算子,大约等于try! -#31954
  • [x] try { ... }表达式-https://github.com/rust-lang/rust/issues/39849

    • [x]解决do catch { ... }语法问题


    • [x]判断catch块是否应该“包装”结果值(首先在https://github.com/rust-lang/rust/issues/41414中解决,现在在https://github.com/rust-中重新结算lang / rust / issues / 70941)

    • []解决类型推断的问题( try { expr? }?当前在某处需要显式类型注释)。

  • [x]解决Try特性的设计(https://github.com/rust-lang/rfcs/pull/1859)

    • [x]实现新的Try特性(代替Carrier )并转换?以使用它(https://github.com/rust-lang/rust/pull / 42275)

    • [x]添加Option impls等,以及合适的测试系​​列(https://github.com/rust-lang/rust/pull/42526)

    • [x]改进RFC(https://github.com/rust-lang/rust/issues/35946)中所述的错误消息

  • [x]在新版本中保留try
  • [x]阻止try{}catch (或其他随后出现的问题)留出将来的设计空间,并指出人们如何使用match做他们想要的事情
A-error-handling B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-try_blocks Libs-Tracked T-lang T-libs

最有用的评论

@ mark-im我认为稳定后我们不能合理地从一个转移到另一个。 正如我认为Ok-wrapping那样糟糕,不一致的Ok-wrapping试图猜测您是否想要它会更糟。

所有340条评论

随附的RFC讨论了基于标记的return / break的减废,我们是否也能理解?还是在编译器中对?catch进行特殊处理?

编辑:我认为标记为return / break是一个与?catch分开的好主意,因此,如果答案为否,我可能会为此单独打开一个RFC。

带标签的退货/退货纯粹是出于解释目的。

2016年2月5日星期五,下午3:56,乔纳森·雷姆(Jonathan Reem) [email protected]
写道:

随附的RFC讨论了基于标记的返回/中断的废止,
我们是否也得到了呢?或者是否会有特殊待遇? 和
赶上编译器?

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

在稳定之前,我们还需要解决的另一个未解决的问题是Into impl s必须遵守的合同是-还是Into甚至是使用的正确特征?对于此处的错误上载。 (也许这应该是另一个清单项目吗?)

@reem

我认为标记为return / break是一个好主意...我可能会为此单独打开一个RFC。

请做!

关于Carrier特质,下面是我在RFC流程的早期写回的一个特例。
https://gist.github.com/thepowersgang/f0de63db1746114266d3

解析期间如何处理?

struct catch {
    a: u8
}

fn main() {

    let b = 10;
    catch { a: b } // struct literal or catch expression with type ascription inside?

}

@petrochenkov嗯,定义不会影响解析,但是我认为我们仍然有一个前瞻性规则,基于{之后的第二个标记,在这种情况下, : ,所以它应该仍然是解析为结构文字。

let c1 = catch { a: 10 };
let c2 = catch { ..c1 }; // <--

struct catch {}
let c3 = catch {}; // <--

+ https://github.com/rust-lang/rfcs/issues/306如果(何时!)已实施。
看起来除了struct文字外没有其他冲突。

给定上面的示例,我使用的是最简单的解决方案(通常)-始终将表达式位置的catch {视为catch块的开始。 无论如何,没人称他们的结构catch

如果使用关键字代替catch会更容易。

这是关键字列表: http :

@bluss是的,我承认它们都不是太好了…… override似乎是唯一一个接近的。 或者我们可以使用do ,嘿。 或两者结合,尽管我没有立即看到任何出色的产品。 do catch

do是似乎与IMO接近的唯一记录。 以do作为前缀的关键字汤有点不规则,与语言的其他部分不同吗? while let还是关键字汤吗? 当您习惯了时,现在感觉还不错。

端口try!以使用?

不能将?移植为使用try!吗? 这将允许您希望获得Result返回路径的用例,例如在调试时。 使用try!相当容易,您只需在文件开头(或lib / main.rs中)覆盖宏:

macro_rules! try {
    ($expr:expr) => (match $expr {
        Result::Ok(val) => val,
        Result::Err(err) => {
            panic!("Error occured: {:?}", err)
        }
    })
}

Result返回路径中第一次出现的try!开始,您将获得一个紧急堆栈跟踪。 事实上,如果你做try!(Err(sth))如果你发现一个错误,而不是return Err(sth) ,你甚至得到完整的堆栈跟踪。

但是,当调试未实现该技巧的人编写的外国库时,人们依赖于try!在链中某个位置的用法。 现在,如果库使用具有硬编码行为的?运算符,则几乎不可能获得stacktrace。

如果重写try!也会影响?运算符,那将很酷。

稍后,当宏系统获得更多功能时,您甚至可能会惊慌! 仅适用于特定类型。

如果此建议需要RFC,请告诉我。

理想情况下,可以将?扩展为直接提供堆栈跟踪支持,而不是依靠覆盖try! 。 然后它将在任何地方都有效。

堆栈跟踪只是一个示例(在我看来,这是一个非常有用的示例)。
如果使运营商特征生效,那么这可以涵盖此类扩展吗?

2016年2月7日星期日,下午4:14,罗素·约翰斯顿[email protected]
写道:

理想地? 可以扩展为直接提供堆栈跟踪支持,
而不是依靠覆盖try!的能力。 这样就可以了
到处。

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

无需推测,尽管有一些问题,我认为它仍然可以工作。

考虑一种常见的情况,即代码返回某个Result<V,E>值。 现在,我们需要允许Carrier特征的多个实现共存。 为了不遇到E0119 ,必须使所有实现超出范围(可能通过默认不导入的不同特征),并且在使用?运算符时,要求用户导入所需的实现实施。

这将要求每个人(即使是不想调试的人)在使用?时导入其希望的特质实现,对于预定义的默认值将没有选择。

可能E0117可太多,如果想要做定制的问题Carrier为实现Result<V,E> ,所有类型的外部界限,所以libstd应提供已提供一组的实现的Carrier具有最常用用例的panic! ing实现,也许更多)。

通过宏进行覆盖的可能性将提供更大的灵活性,而不会给原始实现者带来额外的负担(它们不必导入其期望的实现)。 但是我也看到rust以前从未有过基于宏的运算符,并且如果catch { ... }应该可以工作,则无法通过宏实现? ,至少没有其他语言项( return_to_catchthrow ,其标记为break带有RFC 243中使用的参数)。

我对任何设置都可以,可以使一个人获得具有Err返回路径的Result stacktraces,而只需要修改非常少量的代码(通常位于文件顶部)。 该解决方案还应该与Err类型的实现方式和实现方式无关。

只是为了补充一下自行车棚: catch in { ... }流动非常好。

catch! { ... }是另一个向后兼容的选择。

另外,并不是我希望这会改变任何东西,而是要注意,这将破坏接受$i:ident ?的多臂宏,就像键入ascription破坏$i:ident : $t:ty

不要过度向后兼容,只需在将catch {后面跟着catch作为关键字(可能只在表达式位置,但是我不确定兼容性是否会改变很多)。

我还可以想象一些不涉及结构文字的可能问题(例如let catch = true; if catch {} ); 但我更喜欢对语法进行更小的改动而不是更丑陋的语法。

无论如何,我们不是没有要添加新关键字的吗? 我们可以为新语法提供某种from __future__选择加入; 或在命令行/在Cargo.toml中指定锈语言版本号。
我非常怀疑,从长远来看,我们将只能使用那些已经保留的关键字。 我们不希望关键字根据上下文分别具有三种不同的含义。

我同意。 这甚至不是第一个RFC出现的地方(https://github.com/rust-lang/rfcs/pull/1444是另一个示例)。 我希望这不会是最后一次。 (也来自https://github.com/rust-lang/rfcs/pull/1210的default ,尽管它不是我支持的RFC。)我认为我们需要找到一种添加方法诚实至上的关键字,而不是试图找出如何为每个新案例专门修改语法。

是否不是在1.0之前不保留几个关键字的全部论点,我们肯定会引入一种向后兼容地(可能通过显式选择加入)向语言添加新关键字的方法,所以没有意义吗? 似乎现在是个好时机。

@japaric您是否有兴趣恢复旧的PR并将其继续下去?

@aturon我的实现只是以与try!(foo)相同的方式删除了foo? try!(foo) 。 它也仅适用于方法和函数调用,即foo.bar()?baz()?有效,但quux?(quux)?不起作用。 最初的实施可以吗?

@japaric将它限制为方法和函数调用的原因是什么? 作为一般的后缀运算符,解析起来会更容易吗?

将其限制为方法和函数调用的原因是什么?

(对我而言)测试?扩展的最简单方法

作为一般的后缀运算符,解析起来会更容易吗?

大概

@japaric最好将其概括为一个完整的后缀运算符(如@eddyb所建议的),但可以通过简单的减重操作来插入? ,然后再添加catch

@aturon好吧,如果没有人击败我,下周我将研究其后缀版本:-)。

重新设定/更新了#31954中的PR :-)

如何支持提供堆栈跟踪? 有计划吗

我不愿意成为+1人,但是堆栈跟踪过去节省了我很多时间。 也许在调试版本中,当遇到错误路径时,? 操作员可以将文件/行附加到结果中的Vec吗? 也许Vec也可以只调试吗?

这可能是暴露的,也可能变成了错误描述的一部分...

我一直遇到这样的情况,我想在返回Option<Result<T, E>>迭代器中使用try! / ? Option<Result<T, E>> 。 不幸的是,这目前还没有真正起作用。 我想知道是否可以重载承运人特征来支持此功能,否则会变成更通用的From吗?

@hexsel我真的希望Result<>类型在debug和?中带有vec指令指针。 将附加到它。 这样,DWARF信息可以用于构建可读的堆栈跟踪。

@mitsuhiko但是如何创建和匹配Result ? 这是一个enum atm。

至于Option包装,我相信你想要Some(catch {...})

当前,我现在的习惯是执行try!(Err(bla))而不是return Err() ,以便稍后可以使用panic覆盖try宏,以获得回溯。 它对我来说效果很好,但是我处理的代码级别很低,并且大多数会产生错误。 如果我使用返回Result外部代码,我仍然必须避免? Result

@eddyb除了需要通过其他方式进行操作之外,还需要语言支持来携带隐藏值。 我想知道是否可以通过其他方式完成此操作,但我不知道如何做到。 唯一的其他方法是可以在其上包含其他数据的标准化错误框,但是并不需要装箱错误,并且大多数人不这样做。

@mitsuhiko我可以想到一个关于Error特征和TLS的新(默认)方法。
后者有时被消毒剂使用。

@eddyb仅在可以识别结果并要求将结果装箱或在其向上通过堆栈时将在内存中移动时才起作用。

@mitsuhiko TLS? 并非如此,您只需要能够比较错误按值。

甚至只是按类型(链接From输入和输出),您想要从堆栈跟踪中并发的错误有多少必须同时传播?

当更简单的解决方案起作用时,我个人反对添加特定于Result编译器黑客。

@eddyb ,错误向上传递到堆栈。 您想要的是每个堆栈级别的EIP,而不仅仅是它的起源。 同样,错误是a)当前不可比较的,b)仅因为它们比较相等并不意味着它们是相同的错误。

您要从中获取堆栈跟踪的并发错误数必须同时传播

发现任何错误并重新抛出为其他错误。

当更简单的解决方案起作用时,我个人反对添加针对特定结果的编译器黑客。

我看不到一个更简单的解决方案如何工作,但也许我在那里缺少一些东西。

您可以在每个?保存指令指针,并将其与错误类型相关联。
“发现任何错误,然后将其重新引发为其他错误。” 但是,如果该信息隐藏在Result ,您将如何保存该信息?

但是,如果该信息隐藏在Result中,您将如何保存它?

您不需要将这些信息存储在结果中。 但是,您需要存储的是故障原因的唯一ID,以便您可以将其关联起来。 而且由于错误特征只是一个特征并且没有存储,因此可以将其存储在结果中。 指令指针vec本身绝不必存储在结果中,可以存储到TLS。

一种方法是在结果上调用方法failure_id(&self) ,它返回一个i64 / uuid或标识失败源的信息。

无论如何,都将需要语言支持,因为您需要的是,随着结果向上通过堆栈,编译器将注入一条指令以记录掉入的堆栈帧。 因此,结果的返回在调试版本中看起来会有所不同。

“编译器会注入一条指令来记录掉掉的堆栈帧”-但是?是显式的,这与异常不同,或者您不喜欢仅记录通过的?吗?

无论如何,如果您手动解压缩错误,然后将其放回Err ,那么该ID甚至将如何保存?

“并且由于错误特征只是一个特征并且没有存储,因此可以将其存储在结果中”
这有一个变体:实现Error特性可以在编译器中进行特殊处理,以向该类型添加一个额外的整数字段,创建该类型将触发生成ID,并且复制/删除将有效地增加/减少refcount(如果未使用Result::unwrap则最终从TLS“运行中错误集”中将其清除)。

但这会与Copy特性冲突。 我的意思是,您的计划也是如此,向Result添加任何由?或其他特定用户操作_not_触发的特殊行为,会使Copy不变式失效。

编辑:此时,您最好在其中嵌入Rc<ErrorTrace>
EDIT2 :我什至在说什么,您可以清除catch上的相关错误跟踪。
EDIT3 :实际上,在下拉列表中,请参见以下更好的解释。

“编译器会注入一条指令来记录掉落的堆栈帧”-但是? 是明确的,这与异常完全不同,还是您不喜欢仅录制? 它通过了吗?

那是行不通的,因为您可以使用太多帧而不使用? 。 更不用说并不是每个人都只用?来处理错误。

无论如何,如果您手动解压缩错误,然后将其放回Err,该ID甚至将如何保存?

这就是为什么它必须得到编译器支持。 编译器必须跟踪作为结果的局部变量,并且最好将结果ID向前传播以进行重新包装。 如果这太神奇了,则可以将其限制为一部分操作。

那是行不通的,因为您可以使用太多帧而不使用? 。 更不用说并不是每个人都只用?来处理错误。

好的,我可以看到直接返回Result在具有多个返回路径的复杂函数中是特殊情况(其中有些是?早期返回)。

如果这太神奇了,则可以将其限制为一部分操作。

或完全明确。 您是否有非?重新包装的示例必须由编译器进行魔术跟踪?

@eddyb手动处理错误的常见情况是要处理子集的IoError:

loop {
  match establish_connection() {
    Ok(conn) => { ... },
    Err(err) => {
      if err.kind() == io::ErrorKind::ConnectionRefused {
        continue;
      } else {
        return Err(err);
      }
    }
  }
}

@mitsuhiko然后将ID保留在io::Error肯定可以。

概括一下,TLS中的Vec<Option<Trace>> “稀疏整数映射”,以struct ErrorId(usize)键,并由3个lang项目访问:

  • fn trace_new(Location) -> ErrorId ,创建非const错误值时
  • fn trace_return(ErrorId, Location) ,就在从函数_declared_返回为-> Result<...> (即不是函数泛型,返回的_happens_在那里与Result类型一起使用)
  • fn trace_destroy(ErrorId) ,当删除错误值时

如果变换是在MIR完成, Location可以从计算Span指令触发的误差值的任一结构或写入的Lvalue::Return ,这是更为可靠比指令指针IMO(无论如何,都不容易在LLVM中获得该指令,您必须为每个特定平台发出内联asm )。

@eddyb

那不会导致二进制大小的膨胀吗?

@ arielb1仅在debuginfo会膨胀二进制文件的情况下,才可以在调试模式下进行操作-您也可以巧妙地重用debugsh _shrug_。

@eddyb在这种情况下什么是位置? 不确定读取IP有什么困难。 当然,您需要为每个目标自定义JS,但这并不难。

@mitsuhiko,它可能与我们用于溢出检查和其他编译器发出的紧急情况的信息相同。 参见core::panicking::panic

为什么要在取消展开堆栈时将大量堆栈信息打包到Error / Result ? 为什么不在堆栈仍在的时候运行代码呢? 例如,如果您对堆栈路径上的变量感兴趣? 只要让人们在调用Err时运行自定义代码即可,例如调用他们选择的调试器。 这是try!由于提供的(可重写)宏而已提供的内容。 最简单的方法是在Err情况下惊慌,该情况会打印堆栈,前提是已使用正确的参数启动了该程序。

使用try您可以在Err情况下做任何您想做的事情,并且可以覆盖宏板条箱,或者在范围上不触及性能关键代码,例如,如果错误很难重现和您需要运行许多性能关键代码。

没有人会需要将一部分信息保存在一个人工堆叠中,因为真正的堆叠将被破坏。 所有的宏覆盖方法都可以通过以下改进:

  • ?以类似的方式可以覆盖,最简单的方法是将?定义为try!糖,特别是为了在板条箱边界捕获非原始错误时,在我上面的评论中概述。
  • 具有更强大的宏系统,例如在类型上进行匹配,以提供更大的灵活性。 是的,可以考虑将其放入传统特征系统中,但是生锈不允许覆盖特征的实现,因此这将有些复杂

@ est31堆栈不是像在紧急情况中那样“自动”“退绕”,这是早期返回的语法糖。
是的,您可以让编译器插入一些具有固定名称的空函数调用,您可以在其中断点,这非常容易(并且还具有让您执行操作的信息,例如call dump(data) -其中dumpdata是调试器可以看到的参数)。

@eddyb我认为空函数不允许用例,例如在大型部署中保留一些“ canary”调试实例,以查看日志中是否出现错误,然后返回并进行修复。 我知道主动性比被动性更可取,但并非所有事情都容易预测

@hexsel是的,这就是为什么我更喜欢基于TLS的方法,其中Result::unwrap或一些新的ignore方法(或者甚至在删除错误时总是将错误代码转储到stderr)。

@eddyb如果将指令指针或从该值派生的内容添加到TLS中的堆栈(如TLS中的数据结构),则基本上可以重建实际堆栈的小版本。 返回将堆栈减少一个条目。 因此,如果您这样做是在返回_unwind_堆栈的一部分的同时,在RAM的其他位置构建它的有限版本。 也许“放松”是由“合法”返回所导致的行为的错误术语,但是如果所有代码都执行?try!并且最后截取的最终结果是相同的。 非常棒的是rust不会使错误传播自动进行,我真的很喜欢java如何要求在throws关键字之后列出所有异常,rust对此做了很好的改进。

@hexsel基于覆盖的方法(因为try!已经存在)可以实现此目的-您可以运行所需的任何代码,并登录到任何日志记录系统。 当多个try捕获到同一错误时,您需要对“重复”进行某种检测,因为它会向上传播。

@ est31覆盖try!仅在您自己的代码中起作用(实际上是宏导入阴影),它必须有所不同,例如我们的“弱lang项目”。

@ est31这不是真的正确(关于展开),跟踪和实际堆栈不一定有任何关系,因为移动Result s不必在原始回溯中向上移动,它可以走也要侧身
另外,如果二进制文件经过优化,则大多数回溯都将消失,并且如果您没有调试信息,则Location绝对更好。 您甚至可能正在运行一个模糊的插件,该插件用随机散列替换所有源信息,这些散列可以由某些封闭源产品的开发人员进行匹配。

调试器很有用(顺便说一句,我喜欢lldb的更干净的backtrace输出),但是它们不是万能的,我们已经输出了一些恐慌信息,因此您可以获得一些线索,这是怎么回事。

关于这一点-我对类型系统级别的技巧有一些想法,其中{Option,Result}::unwrap将有一个额外的类型实参,默认情况下,该类型取决于调用函数的位置,从而使那些恐慌产生更有用的位置信息。

随着价值参数化的进步,这可能仍然是一个选择,但绝对不是唯一的选择,我不想彻底消除Result痕迹,相反,我正在尝试寻找一个_implementable_的模型。

覆盖try!根本不是解决方案,因为它包含在您自己的箱子中。 作为调试经验,这是不可接受的。 我已经尝试过很多尝试处理当前的try! 。 尤其是如果您涉及很多板条箱,并且错误在通过堆栈的途中被多次转换,则几乎不可能弄清楚错误的起源和原因。 如果这样的创可贴是解决方案,那么对于大型Rust项目,我们通常必须重新访问错误处理。

@eddyb那么您的建议是我们将文件名,函数名,行号和列号嵌入到该向量中吗? 这似乎是非常浪费的,尤其是因为DWARF中已经以更易于处理的格式包含了该信息。 同样,DWARF使我们可以在生产中相当便宜地使用相同的过程,而这种位置信息似乎非常浪费,没有人会使用它运行发行版二进制文件。

它比DWARF信息浪费得多吗? 文件名将被重复数据删除,在x64上,整个过程只有3个指针的大小。

@mitsuhiko基本上,您同意总体方向,但不同意它的技术细节吗?

将DWARF信息公开给通用Rust API有多容易?

@eddyb,因为DWARF信息不包含在发行版二进制文件中,而是单独的文件。 因此,我可以将调试文件放在符号服务器上,然后将剥离后的二进制文件发送给用户。

@mitsuhiko哦,这与我假设的完全不同。 Rust目前不支持该atm,即AFAIK,但我同意。

您认为指令指针与构建系统为调试发行版二进制文件而生成的随机标识符相比真的有用吗?

我的经验是,除了显式的自我/相互递归和非常大的功能外,任何内联调试器都很难恢复大部分堆栈跟踪。

是的, try!包含在您的箱子中。 如果传递函数指针或类似于库代码的函数,并且函数中存在错误,则try方法仍然有效。 如果返回的Err信息无济于事,则您使用的板条箱有内部错误或错误,您可能需要其源代码才能进行调试。 堆栈跟踪仅在您有权访问代码时才有用,因此封闭的源库(您无法修改其代码)将不得不通过一种或另一种方式来支持。

简单地启用这两种方法,然后让开发人员确定最适合他们的方法呢? 我不否认基于TLS的方法没有任何优势。

该试模型是可实现非常容易,只需desugar ?try ,如果只需要额外的语言扩展catch进来(你会补充说,硬编码的行为中行为的总?

@eddyb “与构建系统为调试发行版二进制文件而生成的随机标识符相比,您认为指令指针实际上有用吗?”

通常,这就是调试本机二进制文件的方式。 目前,我们(哨兵)几乎将其全部用于iOS支持。 我们得到一个故障转储,并使用llvm-symbolizer将地址解析为实际的符号。

@mitsuhiko由于我们已经嵌入了libbacktrace ,我们可以使用它来解析指向源位置的代码指针,因此我并不完全反对它。

@eddyb是的。 只是看着紧急代码。 如果那实际上是Rust代码可以使用的API,那会很好。 我可以看到它在其他几个地方很有用。

关于此问题-我对类型系统级别的技巧有一些想法,其中{Option,Result} :: unwrap将有一个额外的类型实参,默认情况下,其类型取决于调用函数的位置,从而使来自这些函数的恐慌有更多有用的位置信息。

说到...

@glaebhoerl呵呵,也许那真的值得追求? 至少作为一些不稳定的实验。

@eddyb不知道。 也许值得先与一些参与其中的GHC成员讨论它,我只是顺便听说了这一点,并在Google上搜索了一个链接。 而且Rust没有像GHC那样具有实际的隐式参数。 默认类型参数是否可以代替工作是一个有趣的问题(可能是您比我考虑得更多的问题)。

只是想一想:有一个开关会导致rustc为特殊情况,构造一个Err以便在返回之前调用带有有效负载的fn(TypeId, *mut ())函数,这很有用。 。 从基本内容开始就应该足够了,例如根据有效负载过滤错误,如果调试程序发现感兴趣的错误则捕获到调试程序中或捕获某些错误的回溯记录。

PR 33389增加了对Carrier特性的实验性支持。 由于它不是原始RFC的一部分,因此在进入FCP之前,它应该经过特别仔细的审查和讨论(对于?其余操作符,它应该与FCP分开)。 有关更多详细信息,请参见此讨论线程

我反对将?扩展到Option s。

RFC的措辞非常清楚,因为?运算符与传播错误/“异常”有关。
使用Option报告错误是错误的工具。 返回None是成功程序正常工作流程的一部分,而返回Err始终表示错误。

如果我们想改善错误处理的某些领域(例如,通过添加堆栈跟踪),则在Option上实现? Option意味着我们将不得不从更改中排除?

@tomaka我们可以继续在讨论线程中进行总结了您的反对意见)我个人发现,关于GH的漫长讨论变得很笨拙,并且能够将这一特定观点与将来的其他观点或可能出现的问题区分开来也很高兴。

@eddyb这是前面提到的隐式调用GHC功能的发布版本文档。

暂时没有更新。 是否有人在努力推动这一发展? OP中的其余任务仍然准确吗? 这里有什么需要电子帮助的吗?

如果有可能为Rust写一个Sentry客户,那我会在上周末打球。 由于现在大多数错误处理实际上都是基于结果,而不是恐慌,因此我注意到,这样做的用武之地仅限于我决定完全放弃这一点。

我以crates.io代码库为例,尝试将错误报告系统集成到其中。 这使我回到了RFC,因为我真的认为,除非我们能以某种方式记录指令指针,然后将结果传递并在不同的堆栈跟踪中转换,否则将无法获得正确的错误报告。 我已经看到,仅调试本地复杂的逻辑故障会给我带来极大的痛苦,如今我不得不诉诸于错误来自哪里的恐慌。

不幸的是,我目前不了解如何在不对结果的工作方式进行大幅度更改的情况下记录IP。 之前有其他人玩过这个主意吗?

我们在@ rust-lang / lang会议中讨论了这个问题。 一些事情出现了:

首先,确实有兴趣看到?尽快稳定下来。 但是,我想我们大多数人也希望看到?Option ,而不仅仅是Result (但我想不是bool ,因为也已提出)。 关于稳定化的一个担心是,如果我们稳定化而不提供任何允许使用Result以外的类型的特征,那么以后添加它就不会向后兼容。

例如,我自己定期编写这样的代码:

let v: Vec<_> = try!(foo.iter().map(|x| x.to_result()).collect());

我依靠try!通知类型推断,我希望collect返回Result<Vec<_>, _> 。 如果要使用? ,则此推断_might_将来会失败。

但是,在先前的讨论中,我们还决定需要修订RFC,以讨论任何形式的“载体”性状的细节。 显然,该RFC应该尽快编写,但我们不希望在该讨论中不阻止?进度。

我们曾经想到的是,如果我们采用@nrc的实现,并且使特征变得不稳定,并且仅针对Result和某些私有虚拟类型来实现,那应该抑制推理,同时仍然只能使?可与Result

最后一点:我想我们大多数人都希望在Option值上使用? ,它要求函数的返回类型也必须为Option (而不是Result<T,()> )。 有趣的是,这将有助于推理限制,因为在许多情况下,我们最终可以从声明的返回类型中推断出需要哪种类型。

不希望进行相互转换的原因是,它似乎可能导致逻辑松散,类似于即使x具有整数类型,C仍如何允许if x 。 也就是说,如果Option<T>表示一个值,其中None是该值的域的一部分(应如此),而Result<>表示(通常)函数失败为了成功,那么假设None意味着函数应该出错似乎是可疑的(并且像一种任意约定)。 我想那讨论可以等待RFC了。

不希望进行相互转换的原因是,这似乎会导致逻辑松散,类似于即使x具有整数类型,C仍如何允许if x 。 也就是说,如果Option<T>表示一个值,其中None是该值的域的一部分(应如此),而Result<>表示(通常)函数失败为了成功,那么假设None意味着函数应该出错似乎是可疑的(就像一种任意约定)。 我想那讨论可以等待RFC了。

我对此深表赞同。

我们同意提高稳定性的另一个问题是确定From特性应遵循的impl的合同(或我们为Err使用的任何特性-upcasting )。

@glaebhoerl

我们同意进行稳定化处理的另一个问题是,确定From特质应遵守的合同(或我们最终用于Err向上投射的任何特质)都应遵守。

确实。 您能否刷新我的记忆,并从一些您认为应该禁止的事情开始做起? 还是只是您要牢记的法律? 我必须承认我对这样的“法律”保持警惕。 一方面,他们倾向于在实践中被忽视-人们在符合自己目的的情况下利用实际行为,即使行为超出了预期的限制。 这就引出了另一个问题:如果我们有法律,我们会把它们用于任何事情吗? 优化? (不过,对我来说似乎不太可能。)

顺便说一下, catch表达式的状态是什么? 他们实现了吗?

可悲的是没有 :(

在2016年7月26日,星期二,06:41:44AM -0700,亚历山大·布拉维(Alexander Bulaev)写道:

顺便说一下, catch表达式的状态是什么? 他们实现了吗?


您收到此消息是因为您创建了线程。
直接回复此电子邮件或在GitHub上查看:
https://github.com/rust-lang/rust/issues/31436#issuecomment -235270663

也许您应该计划实施它? 没有比接受但未实现的RFC更令人沮丧的了...

cc#35056

仅供参考, https://github.com/rust-lang/rfcs/pull/1450 (枚举变量的类型)将为实现Carrier开辟一些有趣的方式。 例如,类似:

trait Carrier {
    type Success: CarrierSuccess;
    type Error: CarrierError;
}

trait CarrierSuccess {
    type Value;
    fn into_value(self) -> Self::Value;
}

// (could really use some HKT...)
trait CarrierError<Equivalent: CarrierError> {
    fn convert_error(self) -> Equivalent;
}

impl<T, E> Carrier for Result<T, E>
{
    type Success = Result::Ok<T, E>;
    type Error = Result::Err<T, E>;
}

impl<T, E> CarrierSuccess for Result::Ok<T, E> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T, E1, E2> CarrierError<Result::Err<T, E2>> for Result::Err<T, E1>
    where E2: From<E1>,
{
    fn convert_error(self) -> Result::Err<T, E2> {
        Err(self.into())
    }
}

impl<T> Carrier for Option<T>
{
    type Success = Option::Some<T>;
    type Error = None;
}

impl<T> CarrierSuccess for Option::Some<T> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T> CarrierError<Option::None> for Option::None {
    fn convert_error(self) -> Option::None {
        self
    }
}

fn main() {
    let value = match might_be_err() {
        ok @ Carrier::Success => ok.into_value(),
        err @ Carrier::Error => return err.convert_error(),
    }
}

我只是想交叉发表来自https://github.com/rust-lang/rust/pull/35056#issuecomment -240129923的一些想法。 该PR引入了带有伪类型的Carrier特性。 目的是为了保护-尤其是我们想要稳定?而不必稳定其与类型推断的交互。 特质与假人类型的这种组合似乎是安全的。

我的想法是(我认为)我们将编写一个讨论Carrier的RFC并尝试修改设计以使其匹配,仅在我们对整体形状满意时才稳定(或可能删除Carrier (如果我们无法达到自己喜欢的设计)。

现在,更多的是推测性的,我可以预测,如果我们采用Carrier特性,我们将希望禁止运营商之间的相互转换(而该特性基本上是一种转换为Result )。 因此,直观地讲,如果将?应用于Option ,那么如果fn返回Option ; 如果将?应用于Result<T,E> ,则可以使用,如果fn返回Result<U,F> ,其中E: Into<F> ; 但是如果您将?应用于Option且fn返回Result<..> ,那是不正确的。

也就是说,在今天的类型系统中很难表达这种规则。 最明显的起点将是类似HKT的东西(当然,我们实际上并没有,但现在暂时忽略它)。 但是,这显然不是完美的。 如果我们使用它,人们会假设Self参数Carrier有种type -> type -> type ,因为Result可以实现Carrier 。 那将使我们能够表达诸如Self<T,E> -> Self<U,F> 。 但是,它不一定适用于Option ,其类型type -> type (当然,所有这些都取决于我们采用的是哪种HKT系统,但我不认为我们会一直到“通用Lambda”)。 更极端的可能是像bool这样的类型(尽管我不想为布尔实现Carrier ,但我希望有些人可能希望为新类型实现Carrier 'd bool)。

我考虑过的是?的输入规则本身可能很特殊:例如,我们可以说?只能应用于_some_的名义类型Foo<..>类型,并且它将与此类型匹配Carrier特征,但将要求包含fn的返回类型也是Foo<..> 。 因此,我们基本上将使用新鲜的类型参数实例化Foo 。 这个想法的缺点是,如果既不知道要应用?的类型,也不知道封闭的fn的类型,那么在不添加某种新的特征义务的情况下,我们不能强制执行此约束。 这也是临时的。 :)但是可以。

我的另一个想法是,我们可能会重新考虑Carrier特性。 这个想法是让Expr: Carrier<Return>其中Expr是应用类型? ,而Return是环境的类型。 例如,也许看起来像这样:

trait Carrier<Target> {
    type Ok;
    fn is_ok(&self) -> bool; // if true, represents the "ok-like" variant
    fn unwrap_into_ok(self) -> Self::Ok; // may panic if not ok
    fn unwrap_into_error(self) -> Target; // may panic if not error
}

然后将expr?减价至:

let val = expr;
if Carrier::is_ok(&val) {
    val.unwrap_into_ok()
} else {
    return val.unwrap_into_error();
}

这里的主要区别是Target不会是_error_类型,而是新的Result类型。 因此,例如,我们可以添加以下内容:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_ok() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { Err(F::from(self.unwrap_err())) }
}

然后我们可以添加:

impl<T> Carrier<Option<T>> for Option<T> {
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_some() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { debug_assert!(self.is_none()); None }
}

最后,我们可以像这样实现布尔:

struct MyBool(bool);
impl<T> Carrier<MyBool> for MyBool {
    type Ok = ();
    fn is_ok(&self) -> bool { self.0 }
    fn unwrap_into_ok(self) -> Self::Ok { debug_assert!(self.0); () }
    fn unwrap_into_error(self) -> { debug_assert!(!self.0); self }
}

现在,此版本更加灵活。 例如,我们可以通过添加impl来允许Option值之间的相互转换转换为Result ,例如:

impl<T> Carrier<Result<T,()>> for Option<T> { ... }

但是,我们当然不必(而且我们也不必)。

@Stebalien

仅供参考,rust-lang / rfcs#1450(枚举变量的类型)将为实现承运人开辟一些有趣的方式

当我写出我刚才写的那个想法时,我在考虑拥有枚举变量的类型,以及它如何影响事物。

我注意到编写使用?一些代码的一件事是,没有任何类型的“ throw”关键字是很烦人的-特别是如果您编写Err(foo)? ,则编译器不会这样做。 t _know_这将返回,因此您必须编写return Err(foo) 。 没关系,但是如果不自己编写,您将无法获得into()转换。

在以下情况下会出现这种情况:

let value = if something_or_other() { foo } else { return Err(bar) };

哦,我应该再加一件事。 我们允许impls影响类型推断的事实_should_意味着在fn返回Result<..>的情况下, foo.iter().map().collect()? Result<..> ,我怀疑不需要类型注释,因为如果我们知道如果返回类型为Result ,则仅可能应用一个impl(至少在本地)。

哦,我的Carrier特性的一个更好的版本可能是:

trait Carrier<Target> {
    type Ok;
    fn into_carrier(self) -> Result<Self::Ok, Target>;
}

您将在哪里实现它,例如:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn into_carrier(self) -> Result<T, Result<U,F>> {
        match self { Ok(v) => Ok(v), Err(e) => Err(e.into()) }
    }
}

expr?会生成如下代码:

match Carrier::into_carrier(expr) {
    Ok(v) => v,
    Err(e) => return e,
}

当然,这样做的缺点(或好处是...)将Into转换推入了impls,这意味着人们在有意义的时候可能不会使用它们。 但这也意味着如果不需要(对于您的特定类型),可以禁用它们。

@nikomatsakis IMO,特征应为IntoCarrierIntoCarrier::into_carrier应该返回Carrier (一个新的枚举),而不是像这样重复使用结果。 那是:

enum Carrier<C, R> {
    Continue(C),
    Return(R),
}
trait IntoCarrier<Return> {
    type Continue;
    fn into_carrier(self) -> Carrier<Self::Continue, Return>;
}

@Stebalien当然,看起来不错。

提名lang团队会议上的讨论(以及?运算符可能的FCP)。 我认为接下来的几天我们需要在FCP中获得某种临时的运营商特征。

我打开rust-lang / rfcs#1718讨论Carrier特性。

听听,听听! ?运算符现在正进入最终评论期。 讨论大致持续了8月18日开始的发布周期。 倾向于稳定?运算符。

关于承运人特征,一个临时版本降落在#35777中,该版本应确保我们可以通过防止与类型推断发生不必要的交互来自由决定哪种方式。

@ rust-lang / lang成员,请检查您的姓名以表示同意。 留下关注或反对意见。 其他人,请发表评论。 谢谢!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [] @pnkfelix (度假)

我不知道基于tokio的库是否最终会大量使用and_then 。 那就是让foo().?bar()成为foo().and_then(move |v| v.bar())简写的一个论点,以便Results和Future可以使用相同的表示法。

只是要清楚一点,这个FCP是关于question_mark功能,不是catch ,对吗? 本期的标题暗示着在本期中跟踪这两个功能。

@seanmonstar后者甚至尚未实现,是的。 假设如果FCP接受,它将更改为跟踪catch

只是要清楚一点,这个FCP是关于question_mark功能,不是赶上,对吗? 本期的标题暗示着在本期中跟踪这两个功能。

是的,只是question_mark功能。

https://github.com/rust-lang/rfcs/issues/1718#issuecomment -241764523上进行跟进。 我以为当前的行为?可以概括为“结合当前的延续”,但map_err(From::from)的一部分?使它略多于只绑定:/。 如果我们总共为结果添加From实例,那么我想语义可能是v? => from(v.bind(current-continuation))

关于此内部线程中?相对优点的讨论很多:

https://internals.rust-lang.org/t/the-operator-will-be-harmful-to-rust/3882/

我现在没有时间进行深入的总结。 我的记忆是,评论的重点是?是否足够明显以引起人们对错误的关注,但是我可能忽略了讨论的其他方面。 如果其他人有时间总结,那就太好了!

我之前没有发表评论,这也许为时已晚,但是我发现?运算符也令人困惑,如果它用作隐藏的return语句,正如@hauleth在讨论中指出的那样,您已经链接了@ nikomatsakis。

使用try! ,很明显某个地方可能有返回值,因为宏可以做到这一点。 将?作为隐藏的return ,我们将有3种方法从函数返回值:

  • 隐性收益
  • 显性收益
  • ?

我确实喜欢这样,正如@CryZe所说:

这样,每个人都熟悉它,将错误归纳到最后,您可以在其中进行处理,并且没有隐式的回报。 所以它大概看起来像这样:

让a = try!(x?.y?.z);

这样既可以使代码更简洁,又不会隐藏返回值。 而且它是其他语言所熟悉的,例如coffeescript

使?在表达式级别而不是函数级别解析会对期货产生怎样的影响? 对于所有其他用例,对我来说似乎还可以。

我确实喜欢这样,正如@CryZe所说:

这样,每个人都熟悉它,将错误归纳到最后,您可以在其中进行处理,并且没有隐式的回报。 所以它大概看起来像这样:

让a = try!(x?.y?.z);

我已经假设了这一点。 我认为这将是完美的解决方案。

使用try !,很明显某个地方可能会有返回,因为宏可以做到这一点。

这仅是显而易见的,因为您熟悉Rust中的宏工作方式。 一旦?稳定,广泛并且在Rust的每个简介中都将进行解释,情况将完全相同。

@conradkleinespel

我们将有3种方法从函数返回值:

  • 隐性收益

Rust没有“隐式回报”,它具有可计算为值的表达式。 这是一个重要的区别。

if foo {
    5
}

7

如果Rust具有“隐式回报”,则该代码将被编译。 但这不是,您需要return 5

一次将完全相同? 稳定,广泛并且在Rust的每个简介中都有解释。

有关其外观的示例,请参见https://github.com/rust-lang/book/pull/134

使用try !,很明显某个地方可能会有返回,因为宏可以做到这一点。
这仅是显而易见的,因为您熟悉Rust中的宏工作方式。 一次将完全相同? 稳定,广泛并且在Rust的每个简介中都有解释。

用我知道的任何语言,“宏”都意味着“龙在这里”,并且有可能发生任何事情。 因此,我将其重新表述为“因为您熟悉宏的工作原理”,而没有“在Rust中”部分。

@hauleth

让a = try!(x?.y?.z);

我已经假设了这一点。 我认为这将是完美的解决方案。

我非常不同意。 因为您将获得一个仅在try!而非外部可用的魔术符号。

@hauleth

让a = try!(x?.y?.z);
我已经假设了这一点。 我认为这将是完美的解决方案。
我非常不同意。 因为您将获得一个仅可尝试使用的魔术符号! 而不是外面。

我没有说过?应该只在try!起作用。 我的意思是?应该像管道运算符那样工作,它将数据向下推并在发生错误时立即返回错误。 在这种情况下,不需要try! ,但是可以在与现在使用的上下文相同的上下文中使用。

@steveklabnik我认为Rust是一种具有隐式返回的语言,在您的示例中, 5未被隐式返回,但是让我们这样做:

fn test() -> i32 {
    5
}

在这里, 5是隐式返回的,不是吗? 与return 5; ,您的示例将需要。 这有两种返回值的不同方式。 我对Rust感到有些困惑。 增加三分之一将对IMO没有帮助。

它不是。 它是表达式的结果,特别是函数体。 “隐式返回”意味着您可以以某种方式从任何地方隐式返回,但这不是事实。 没有其他基于表达式的语言将其称为“隐式返回”,因为这就是我上面的代码示例。

@steveklabnik好,谢谢您抽出

都很好! 我完全可以看出您来自哪里,这只是人们经常错误使用的两种不同事物。 我已经看到人们认为“隐式回报”意味着您可以将;保留在源代码中的任何地方以返回.... _would_非常糟糕:smile:

@hauleth吗? 在这种情况下,运算符只是and_then的语法糖。 这样一来,您就可以在更多情况下使用它,而不必担心错过回报。 这也是所有其他语言都具有的语言吗? 操作员。 锈的? 当前实现中的运算符将与其他所有语言完全相反。 另外,and_then是功能性方法,无论如何都应鼓励使用,因为它具有明确的控制流程。 所以只是制作? and_then的语法糖,然后保持当前尝试! 显式地“解开并返回”,似乎使情况更加清洁,使返回更明显,并且? 运算符更加灵活(可以在模式匹配之类的非返回情况下使用它)。

究竟。

ŁukaszNiemier
[email protected]

Wiadomośćnapisana przez Christopher Serr [email protected] w dniu 02.09.2016,上帝。 21:05:

@hauleth https://github.com/hauleth吗? 在这种情况下,运算符只是and_then的语法糖。 这样一来,您就可以在更多情况下使用它,而不必担心错过回报。 这也是所有其他语言都具有的语言吗? 操作员。 锈的? 当前实现中的运算符将与其他所有语言完全相反。 另外,and_then是功能性方法,无论如何都应鼓励使用,因为它具有明确的控制流程。 所以只是制作? and_then的语法糖,然后保持当前尝试! 显式地“解开并返回”,似乎使情况更加清洁,使返回更明显,并且? 运算符更加灵活(可以在模式匹配之类的非返回情况下使用它)。

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244461722上查看,或忽略线程https://github.com/notifications/unsubscribe-auth/ AARzN5-w4EO9_FwNMDpvtYkGUuQKGt-Kks5qmHOHgaJpZM4HUm_-。

当处理Rust仓库的请求时,我实际上必须使用使用?的代码。 操作符,实际上,它确实损害了我的可读性,就像被超级隐藏了(在精神上,因为只是噪音被大脑过滤掉了),而我却忽略了很多。 我觉得这很吓人。

@steveklabnik我们称之为“隐性收益”,因为我们都没有唯一的人

@hauleth呵呵,在我使用Ruby的所有岁月中,我从未听说过有人将其称为隐式返回。 我仍然坚持认为这是错误的思考方式。

我在一些项目中使用了? ,并且将其首选为try! ,主要是因为它处于后缀位置。 通常,“真实的” Rust代码几乎在main处进入Result 'monad',并且除了偶尔的叶子节点外,从不离开它。 这样的代码总是可以传播错误。 在大多数情况下,哪个表达式会产生错误并不重要-它们都只是被发送回堆栈,而且当我阅读代码的主要逻辑时,我也不想看到它。

我对?主要担心是,如果方法宏存在的话,我可以获得相同的好处-后缀位置-。 我还有其他担忧,也许是通过稳定当前的公式,我们限制了错误处理中将来的表达方式-当前的Result转换不足以使Rust在错误处理方面达到我想要的人机工程学; 我们已经用Rust错误处理方法犯了一些设计错误,这些错误看起来很难解决,这可能会使我们更深入地了解我们。 尽管我没有具体证据。

我之前写过很多次,但是我绝对爱上?和载体特征的可能性。 我将一个项目转换?完全使用try!来说太复杂了。 我还很有趣地浏览了其他一些项目,以了解它们如何处理? ,总的来说,我还没有遇到任何问题。

因此,在一个更好的Carrier特性的基础上,我给出了+1来稳定? ,它在理想情况下还涵盖了我在其他讨论中提到的其他一些情况。

我主要关心的是? 如果存在方法宏,我可以获得相同的好处-后缀位置-。

也许我们需要一个RFC? 大多数人似乎喜欢?的功能,但不喜欢?的功能。 本身。

也许我们需要一个RFC? 大多数人似乎喜欢?的功能,但不喜欢?的功能。 本身。

一个RFC,对此
在合并RFC之前,已经对?进行了大量讨论,作为支持者,在讨论稳定性时,必须做同样的事情很累。

无论如何,我会在这里为@mitsuhiko的观点输入+1。

RFC对此有很多讨论。 另外,我不知道您从哪里得到“大多数人”。 如果是来自本期的参与者,那么您当然会看到更多人在争论,因为稳定已经是团队的默认行为。

抱歉,我的评论太简短了。 我指的是为某种“方法宏”创建RFC,例如func1().try!().func2().try!() (据我所知,目前尚无法实现)。

我个人喜欢吗? 运算符,但我与@brson有相同的担忧,并且我认为在稳定此功能之前探索替代方法会很不错。 包括RFC转换,该线程和@nikomatsakis链接的内部线程,即使此功能一遍又一遍,但肯定仍存在一些关于此功能的争论。 但是,如果没有可行的替代方案,那么稳定确实是最有意义的。

在没有完全实现某个功能的情况下稳定其功能似乎为时过早-在这种情况下,是catch {..}表达式。

我之前曾对此功能表示过担忧,但我仍然认为这是个坏主意。 我认为拥有后缀条件返回运算符不同于任何其他编程语言中的任何东西,并且正使Rust超出其已经很复杂的复杂性预算。

@mcpherrinm其他语言在每次调用时都隐藏了展开路径以进行错误处理,您是否将operator()称为“条件返回运算符”?

至于复杂度预算,它在语法上与try!只是语法上的不同,至少是您抱怨的部分。
是否反对try! -繁重的代码(仅使?更具可读性)?
如果是这样的话,那么我会同意是否还有一个严肃的选择,那就是“根本没有错误传播自动化”。

提出妥协建议: https :

它可能没有机会被接受,但是无论如何我都在尝试。

我喜欢@keeperofdakeys关于“方法宏”的想法。 我不认为?语法由于三元运算符不会生锈-可读性的原因而被接受。 ?本身什么也没说。 相反,我宁愿看到使用“方法宏”将?的行为概括化的能力。

a.some_macro!(b);
// could be syntax sugar for
some_macro!(a, b); 
a.try!();
// could be syntax sugar for
try!(a); 

这样,很清楚行为是什么,并且可以轻松链接。

result.try!()这样的方法宏似乎是对语言人体工程学的更一般的改进,并且比新的?运算符更特别。

@brson

我还有其他担忧,也许是通过稳定当前公式来限制将来在错误处理中的表现力-当前的结果转换不足以使Rust在错误处理方面达到我想要的人体工程学

这是一个有趣的观点。 值得花一些时间在此上(也许您和我可以聊一点)。 我同意我们可以在这里做得更好。 提议的Carrier特质设计(请参阅https://github.com/rust-lang/rfcs/issues/1718)在这里可能会有所帮助,特别是与专业化结合使用时,因为它使事情变得更加灵活。

我真的怀疑方法宏是否会很好地扩展该语言。

macro_rules!宏当前以类似于释放函数的方式声明,并且在采用新的导入系统时将变得更加类似。 我的意思是,它们像顶级项目一样被声明,并且像顶级项目一样被调用,并且很快它们也将像顶级项目一样被导入。

这不是方法的工作方式。 方法具有以下属性:

  1. 不能在模块范围内声明,但必须在impl块内声明。
  2. 使用与impl块关联的类型/特征导入,而不是直接导入。
  3. 根据其接收者类型进行调度,而不是根据该范围内的单个明确符号进行调度。

由于宏在类型检查之前已展开,因此,就我所知,使用方法语法对宏而言,这些属性都不适用。 当然,我们可以拥有使用方法语法的宏,但是这些宏的分发和导入方式与“免费”宏相同,但是我认为这种差异会使该功能变得非常混乱。

由于这些原因,我认为延迟?并不是一个好的选择,因为它认为“方法宏”有一天会出现。

而且,我认为在某些领域,这种构造被广泛使用并且非常重要,应该将其从宏升级为糖。 for循环是一个很好的例子。 ?行为是Rust的错误处理故事不可或缺的一部分,我认为将其用作一流糖而不是宏是合适的。

@hauleth@CryZe

为了回应有人建议?应该是and_then运算符,由于它们广泛使用扩展功能,因此在像Kotlin(我不熟悉coffeescript)之类的语言中效果很好如此简单的生锈。 基本上,大多数and_then用途不是maybe_i.and_then(|i| i.foo()) ,而是maybe_i.and_then(|i| Foo::foo(i))前者可以表示为maybe_i?.foo()而后者则不能。 可能会说Foo::foo(maybe_i?, maybe_j?)变成maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j)))但这比仅仅说击中第一个导致错误的?生锈早期回报还令人困惑。 但是,这无疑会更强大。

@Stebalien在接受的RFC中, catch { Foo::foo(maybe_i?, maybe_j?) }做您想要的。

@eddyb好点。 我想我可以省略“但是可以说它会更强大”。 归结为隐式捕获/显式尝试与显式捕获/隐式尝试:

let x: i32 = try Foo::foo(a?.b?.c()?));
let y: Result<i32, _> = Foo::foo(a?.b?.c()?);

与:

let x: i32 = Foo::foo(a?.b?.c()?);
let y: Result<i32, _> = catch  Foo::foo(a?.b?.c()?);

(模语法)

@Stebalien另一个示例:如果我想将Foo传递给函数bar ,并附上您的提案,则需要:

bar(Foo::foo(a?.b?.c()?)?)

这是您的想法吗? 请注意,额外的? ,如果没有它,则bar会得到Result而不是Foo

@eddyb可能。 注意:我实际上并不是在提议! 我认为使用?作为管道运算符在没有某种方式处理Foo::foo(bar?)情况下对生锈不是特别有用。

只是要注意,我讨厌方法宏的想法,而且我想不出我会更坚决反对的语言功能。 它们模糊了编译器的阶段,除非我们对语言进行真正的根本改变,否则它们就不可能存在并且不会令人惊讶。 它们也很难合理地解析,并且几乎可以肯定不向后兼容。

@Stebalien ,将?作为管道运算符Foo::foo(bar?)看起来像这样: Foo::foo(try!(bar))bar(Foo::foo(a?.b?.c()?)?) (假设Foo::foo : fn(Result<_, _>) -> Result<_, _> ): bar(try!(Foo::foo(a?.b?.c()?)))

@hauleth我的观点是,与锈蚀中的bar?.foo()?相比, Foo::foo(bar?)?更加普遍。 因此,有用的是, ?必须支持这种情况(或者必须引入一些其他功能)。 我正在假设一种这样做的方式,并且表明这种方式至少会很混乱。 ?的全部要点是避免写try!(foo(try!(bar(try!(baz()))))) (括号的2倍!); 通常不可能将其重写为try!(baz()?.bar()?.foo())

但是您可以随时执行以下操作:

try!(baz().and_then(bar).and_then(foo))

ŁukaszNiemier
[email protected]

Wiadomośćnapisana przez Steven Allen [email protected] dniu,2016年5月9日。 15:39:

@hauleth https://github.com/hauleth我的意思是Foo :: foo(bar?)? 比bar?.foo()更常见? 在生锈。 因此,有用吗? 必须支持这种情况(或必须引入一些其他功能)。 我正在假设一种这样做的方式,并且表明这种方式至少会很混乱。 整点? 是为了避免编写try!(foo(try!(bar(try!(baz())))))))(2x括号!); 通常不可能将其重写为try!(baz()?. bar()?. foo())。

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244749275上查看,或忽略线程https://github.com/notifications/unsubscribe-auth/ AARzN1Hdk6uk5-SoYawtgAbJUDf_8MsMks5qnBumgaJpZM4HUm_-。

稍微相关一点,似乎?功能主要由构建器使用,因此我们可以通过提供一种简单的方法来构造包装Result的构建器来避免使用-功能。 我在这里提出了一些建议,但可能需要做更多的工作。

https://github.com/colin-kiegel/rust-derive-builder/issues/25

感谢您对方法宏提示@nrc@withoutboats的想法,很高兴听到一些无法使用它们的具体原因。

@nielsle我不认为?是建筑商“主要”使用的准确说法。 虽然我以构建器为例,但我认为轻量级的后缀运算符的优点真正发挥了作用,但在每种情况下,我都希望?胜于try!

@nielsle关于期货,我最初出于类似原因而受到关注。 但是,在考虑之后,我认为在这种情况下, async / await将取代对?任何需求。 它们实际上是正交的:您可以执行(await future)?.bar()

(也许最好有一个后缀运算符而不是关键字await所以不需要括号。或者精心调整的优先级就足够了。)

我绝对希望在稳定之前能看到一些文档。 我看了参考资料,找不到任何提及。 我们应该在哪里记录此功能?

@cbreeden我知道@steveklabnik通常避免记录不稳定的特征,因为如果它们永远无法稳定,那很可能会浪费时间。 我不知道我们以前是否曾经阻止过稳定文档的编写。

@solson你是对的,这可能不是提起它的地方-或至少不应该与稳定问题相关。 我想我只是在想象这样一种情况,我们可以决定稳定某个功能,但是在发布稳定的rustc之前还需要文档。 有一个与将文档与功能稳定和发布相集成有关的RFC,因此我将等待该过程稳定下来(当然,首先要准备好适当的文档)

我认为该RFC的重要部分是在表达式的右侧具有类似于try! ,因为这使得读取“ try” _much_的顺序/链接用法更具可读性并具有“ catch” 。 最初,我是100%的支持者,使用?作为语法,但是最近我偶然发现了一些已经使用?代码(干净!),这使我意识到,除了简单的示例?非常容易被忽略。 现在,我相信使用?作为“新尝试”的语法可能是一个大错误。

因此,我建议最好在定稿之前(在论坛上对其进行通知)输入一些poll_以获得关于使用?或其他一些符号作为语法的反馈。 最好是带有较长功能的示例。 请注意,我只是考虑将?重命名而不更改其他内容可能是个好主意。 该民意调查可以列出过去确实出现过的其他可能的名字,例如?! (或?? ),或者只是诸如“使用?vs.在字符上使用更多”之类的东西。

顺便说一句。 不使用?也可能会让不喜欢它的人满意,因为它的语法与其他语言的可选类型相同。 以及那些想要使其成为rust Option类型的可选语法的人。 (尽管这与我分享的问题无关)。

此外,我认为通过这样的民意测验,通常可以达到不参与RFC流程的人们。 通常,这可能不是必需的,但是try! => ?对于任何编写和/或阅读生锈代码的人来说都是一个很大的变化。

PS:
通过不知道是否还有更多功能,我用不同的变体(“?”,“?!!”,“ ??”)竖起了具有上述功能的要点。 还有一个用于将?重命名?!RFC ,它被重定向到此讨论。

抱歉,可能会重新开始一个已经持续很长时间的讨论:smiley_cat:。

(请注意,如果您仍想为Option引入? ,则??是不好的,因为expr???会含糊)

?非常容易被忽略

我们在这里讨论什么? 完全未突出显示的代码? 定期突出显示代码浏览?
还是在函数中积极寻找?

如果我在编辑器中选择了一个? ,则文件中的其他?会以亮黄色背景突出显示,并且搜索功能也起作用,因此我看不到最后一个对我来说构成任何困难。

至于其他情况,我宁愿通过更好地突出显示来解决,而不是使用???!

@dathinab我认为.try!()甚至会更好,但这需要使用UFCS作为宏。

let namespace = namespace_opt.ok_or(Error::NoEntry).try!();

这样就很难错过,但是和.unwrap()一样容易,甚至更容易输入。

@eddyb :关于普通的突出显示的代码,例如在github上。 例如,阅读代码库时。 从我的角度来看,如果我需要强烈地突出显示以不轻易忽略? ,这将是一种错误,因为这会引入另一个返回路径(/ catch的路径)并可能从Result<T>更改变量的类型。 T

@CryZe :我同意你的看法,但是我认为我们不会在不久的将来得到这个。 而且拥有比.try!()短一点的东西也不错。

@CryZe我也喜欢这种语法,但是@withoutboats提到了方法宏可能会损害语言的一些可靠原因。
另一方面,我担心如果方法宏确实出现,我认为它们不能与?

我并不是天生就反对使用两个字符作为标记,但我查看了示例,但没有发现所做的更改使?对我而言更加明显。

是的,也是。 我认为它必须是某种关键字,因为通常在正常语言和编程语言中,符号通常都用于结构化。

@CryZe :也许像?try这样的东西可以作为关键字。 但是说实话,我不喜欢它( ?try )。

@eddyb

搜索功能也可以

搜索?会出现误报,而较长的版本很可能不会。

正在搜寻? 会出现误报,而较长版本则不会。

我希望语法荧光笔可以在内部进行处理(避免误报)。 实际上,他们甚至可以在边缘(行号旁边)插入某种形式的“可能返回”标记。

例如,
screen-2016-09-15-175131

哇,绝对有很大帮助。 但是那时候你真的需要确保你
正确配置您的编辑器,而您却不能一直这样做(例如
例如在GitHub上)。

2016-09-15 23:52 GMT + 02:00史蒂文·艾伦[email protected]

例如,
[image:screen-2016-09-15-175131]
https://cloud.githubusercontent.com/assets/310393/18568833/1deed796-7b6d-11e6-99af-75f0d7ddd778.png

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

@CryZe我很确定GitHub仅使用了一些开源语法突出显示; 我们可以修补它:-)。

我认为,通常来说,Rust项目建议Rust的语法突出显示工具应在?上使用高度可见的样式,以平衡对可见性的关注,这是一个好主意。

我认为?是我们可以在此处做出的最佳选择,我们在Miri中已经大量使用了它。

回到最初的动机, try!(...)太过引人注目,很难忽略错误的流向,只是阅读幸福的道路。 与传统语言的隐形例外相比,这是一个缺点。 将?扩展到更复杂的关键字将具有相同的缺点。

另一方面,使用? ,当我不在乎错误流时,我可以忽略它并使它淡入背景。 当我真正关心错误的流向时,我仍然可以看到?很好。 明亮地突出?对我来说甚至不是必需的,但是如果它可以帮助其他人,那太好了。 这是对可见异常和try!

切换到像?!这样的小符号不会对我有任何帮助,但会使读取和写入错误处理代码的情况稍差一些。

谢谢大家的热烈的最终意见征询期(以及之前的内部线程)。 鉴于我们正在谈论迄今为止评论最多的RFC ,因此看到有关稳定的讨论也非常活跃,我并不感到惊讶。

首先让我开始追逐: @ rust-lang / lang团队已决定将?运算符应用于Result类型的值时,使其稳定。 请注意, catch功能尚未稳定(实际上尚未实现); 同样,所谓的“运营商特征”,也就是将?扩展到Option仍处于RFC之前的讨论阶段。 但是,我们在当前的实现中已采取步骤,以确保以后可以添加Carrier特性(这解决了我之前有关推理潜在交互的一些担忧)。

我想花一些时间来总结自FCP于8月22日开始以来原始RFC线程中。 如果您有兴趣阅读该话题,则该话题中的FCP注释摘要注释将尝试深入讨论会话。 在某些情况下,如果它们比该线程中的相应注释更深入,我将链接至该原始线程中的注释。

?运算符的范围应该是当前表达式,而不是当前函数。

当发生错误时,今天的try!宏将该错误无条件传播到调用函数(换句话说,它执行带有错误的return )。 按照当前的设计, ?运算符遵循此先例,但其目的是支持允许用户指定更有限范围的catch关键字。 例如,这意味着x.and_then(|b| foo(b))可以写为catch { foo(x?) } 。相反,几种最新语言使用?运算符来表示类似于and_then ,并且担心这可能会使新用户感到困惑。

最终,一旦实现catch这就是defaults的问题。 而且,有几个原因使我们认为,“突破功能”的默认设置(带有自定义选项)更适合Rust中的?

  • try!宏已经多次证明自己是一个非常有用的默认值?旨在替代try! ,因此它的行为自然是相同的。
  • Rust中的?主要用于传播结果,它更类似于异常。 所有异常系统默认都将错误从当前函数传播到调用者(即使是那些像Swift一样也需要关键字的错误)。

    • 相反,Swift,Groovy等中的?与空值或选项类型有关。

    • 如果我们采用运营商特征,Rust中的?运算符仍然可以在上述情况下使用(尤其是与catch结合使用),但这不是_main_用例。

  • 今天在Rust中使用and_then不能使用方法符号; 在任何情况下,某些可预见的未来用途(例如在将来)都可能

?掩盖了控制流,因为它很难发现。

一个普遍的担忧是?运算符太容易忽略了。 这显然是一种平衡行为。 有一个轻量级的运营商可以很容易地专注于“幸福路”,当你想上-但有可能发生错误(与例外,它采用隐式控制流)一些迹象是很重要的。 此外,它很容易使?容易通过语法高亮现货(如, 12 )。

为什么不使用方法宏?

?一大好处是可以在定位后使用,但是
我们可以从“方法宏”(例如foo.try!获得类似的好处。 确实如此,方法宏本身会带来很多复杂性,尤其是如果您希望它们的行为类似于方法(例如,根据接收方的类型调度,并且不使用词法作用域)。 此外,使用像foo.try!这样的方法宏比foo?具有明显更重的重量感(请参阅上一点)。

From提供哪些合同?

在最初的RFC讨论中,我们决定推迟[是否应该为From订立合同]((https://github.com/rust-lang/rust/issues/31436#issuecomment -180558025)。lang团队的普遍共识是,应该将?运算符视为调用From特性,并且From特性暗示自然可以做任何事情请注意,无论如何, From特征的作用在这里非常有限:它仅用于将一种错误转换为另一种错误(但始终在Result的上下文中)

但是,我想指出的是,有关“携带者”特征的讨论仍在进行中在这种情况下采用更强的约定更为重要。 特别是,承运人特征可以定义一种类型的“成功”和“失败”的构成,以及一种值(例如Option )是否可以转换为另一种值(例如a Result )。 许多人认为,我们不希望在“类似错误”的类型之间?不能将Option转换Result )。 显然,由于最终用户可以根据自己的类型实现Carrier特性,因此这最终是一个准则,但我认为这是一个重要的准则。

端口试试! 使用?

我认为我们永远无法向后兼容,我们应该不考虑try! 。 我们应该弃用try!吗?

@nrc为什么不向后兼容?

@withoutboats try!(x)(x : Result<_, _>)? ,如果我们想要去做,我们可能可以这样实现,但是总的来说, x?可以推断出任何支持Carrier特质(将来),例如iter.collect()? ,它与try!只是Result但实际上可以是Option

这就说得通了。 我以为我们接受了在std上添加impls可能导致推理含糊的问题。 为什么不在这里也接受呢?

无论哪种方式,我都认为应该弃用try!

? 在诸如上下文的构建器模式中更有用,而在诸如上下文的嵌套方法中,try更有用。 我认为它不应该被弃用,这意味着什么。

@ est31除了推断外,他们现在所做的完全相同。 不确定您的确切意思,但?通常严格干净(再次进行模推断)。 你能举个例子吗?

@eddyb foo()?.bar()?.baz()try!(try!(foo()).bar()).baz() ,而try!(bar(try!(foo())))bar(foo()?)?

在这两种情况下,我发现?更具可读性。 它减少了不必要的括号混乱。

什么时候能稳定下来?

@ofek这是整个事情,还没有完成,所以很难说。 https://github.com/rust-lang/rust/pull/36995稳定了基本的?语法,该语法应在1.14中保持稳定。

别忘了添加吗? 操作员对其稳定的参考: https: //doc.rust-lang.org/nightly/reference.html#unary -operator-expressions

@bluss指出这本书也已经过时了: https : //doc.rust-lang.org/nightly/book/syntax-index.html

@tomaka

我反对扩展? 选项。

它不必用于错误目的。 例如,如果我希望使用一个包装器方法来获取get并通过某些函数将其映射,那么我希望能够传播None情况。

? 被认为只是为了错误; 像这样的一般目标的一般传播符号就不是目标。

2016年10月29日,11:08 -0400,ticki [email protected] ,写道:

@tomaka (https://github.com/tomaka)

我反对扩展? 选项。

它不必用于错误目的。 例如,如果我希望通过某种方法获取get并映射它的包装方法,那么我希望能够传播None情况。

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub(https://github.com/rust-lang/rust/issues/31436#issuecomment-257096575)上查看,或使线程静音(https://github.com/notifications/unsubscribe) -auth / AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-)。

这(= ?本身)现在是一个稳定的功能! (来自Rust 1.13)

两个文档问题:

  • [x]书#37750中的
  • [x]针对当前情况更新Carrier特质文档#37751

请注意, catchCarrier特性都尚未正确实现,仅具有裸露的?功能。

Carrier存在,并且在使用?时错误消息引用了它,因此,如果已解决载波问题而不是被拒绝,会更好。 它的文档也需要更新,因为该文档指的是为Option实现的文档。 (这是错误的)。

35946应该从错误消息中删除对Carrier任何提及。 听起来我们至少应该从Carrier文档中删除Option提及。

由于与Carrier特性的相互作用,我向此问题添加T-libs

@cramertj遇到的问题: https : //internals.rust-lang.org/t/grammatical-ambiguity-around-catch-blocks/4807

你好在#31954中,错误类型的向上转换是使用From (与当前head中的操作类似),但是RFC 243明确指出应将Into用于转换。

有什么理由代替使用From ? 当尝试转换为某些常规错误类型(即io::Error或外部包装箱中的其他内容)时,无法为本地错误类型实现From

(作为RFC 243的作者的FWIW,我对FromInto是可取的选择并没有很认真的考虑,并且可能会或不会做出正确的选择。该问题应基于优点(此时可能包括向后兼容性)而不是RFC中写的内容决定。

可悲的是,这将是一种回归。 如果没有(非平凡) From<...>表示错误类型的实例被实现,编译器可以推断出在某些情况下的类型,它可以不使用时推断它Into (该组的From实例受当前板条箱限制,编译板条箱时不知道完整的Into实例集)。

请参阅https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 (从src / librustc / util / ppaux.rs#L81中涉及fmt::Error的实际问题中“简化”)

在描述?方面,有一个新的RFC将取代旧的RFC:rust-lang / rfcs / pull / 1859

我对拥有catch块有个-1,而它们对于闭包已经更加简洁了。

闭包会干扰流控制语句。 ( breakcontinuereturn

既然https://github.com/rust-lang/rfcs/pull/1859已合并,并声称我们正在将此问题作为跟踪问题重复使用,我想建议我们重新评估catch RFC 243的

我并不是建议我们彻底摆脱它,但是对catch热情从来没有像对?那样热情,我认为值得确保它仍然有意义已经出现了?的惯用语中的一个,并且有望根据Try

我仍然渴望被抓住; 今天早些时候,我不得不以一定的方式扭曲某些代码,如果catch保持稳定,那是可以避免的。

该功能已在每晚使用do catch语法实现,不是吗?

@withoutboats是的,当前do catch ,因为catch { ... }与struct文字( struct catch { }; catch { } )冲突。

@archshift正如@SimonSapin上面指出的那样,闭包会干扰breakcontinuereturn

@bstrie我发现这些天我经常想要catch ; 在重构过程中会出现很多问题。

我没有意识到我们打算使用do catch作为语法。 鉴于实际破坏的风险似乎极低(都违反了结构命名准则,并且必须使构造函数成为该语句中的第一个表达式(在返回位置之外很少见)),我们是否可以利用rustfmt来将任何有问题的标识符重写为catch_吗? 如果它需要Rust 2.0这样做,那么,我一直都是说Rust 2.0应该只包含微不足道的重大更改...:P

@bstrie我们绝对不想长期要求do catch 。 导致现在使用该语法的讨论在这里: https :

太好了,谢谢你的帮助。

我来这里是因为我希望catch是稳定的,并且不得不学习它不是-所以是的,绝对的,现在?是稳定的,同时拥有catch

我想看看我们在剩下的事情上走了多远,并看到了讨论要点。

我一直在考虑一个可能很愚蠢的想法,这种想法在这种情况下会有所帮助:允许选择在关键字前面加上@或其他符号,然后使所有新关键字仅使用该符号。 协程讨论中我们也有类似的问题。 我不记得我是否曾经在这里作为解决方案(我可能会这样做),但看起来这种情况可能会不断出现。

FWIW,C#实际上支持相反的说法: @用于将关键字用作标识符。 这在Razor中很常见,您可以在其中传递new { <strong i="6">@class</strong> = "errorbox" }来设置HTML节点上的属性。

@scottmcm
那很有意思。 我不知道但是对于Rust,我们必须走另一条路,因为兼容性。

他们的想法很整洁。 .net生态系统具有多种语言,所有语言都有不同的关键字,并且都可以相互调用。

关键字未来的另一种可能性是:将它们放在属性后面,例如某种稳定的#[feature]

我认为从长远来看,这个问题将需要一个更通用的解决方案。

@camlorn该主题可能会让您感兴趣,尤其是亚伦关于Rust“时代”的想法: https : //internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

我认为我们应该从这里的问题描述中链接https://github.com/rust-lang/rust/issues/42327 。 (也许RFC文本应该更新为链接到那里而不是这里。)

(编辑:我在那儿发表了评论,不确定谁已经订阅了!)

@camlorn但是,它可以打开“ rustfmt进行一小段重写,然后将其变成关键字”路径。 AKA充分利用了“如果可以编写出一个额外的注释,使它在两种情况下均能正常工作,那么它不会中断”,它可以确保稳定性。 而且,类似于UFCS进行推理更改,一种假设的“完全修饰形式的存储”模型可以使旧包装箱保持运转。

我不介意catch块的语法只是do { … }甚至?{ … }

do具有已经是关键字的不错的属性。 它具有调用类似Haskell的do标记的可疑(取决于您的观点)属性,尽管它从未停止过其过去的使用,并且此用例在用途上更接近。

它也看起来像,但行为与javascript建议的do表达式不同

这个提议与Rust并没有真正的关系,Rust已经使用裸块作为表达式了。

我只是指出了可能的混淆,因为两者看起来相同,却做的事情完全不同。

它具有调用类似Haskell的do-notation的可疑(取决于您的观点)属性

@rpjohnst Result和Option是monad,因此至少在概念上相似。 它也应该是向前兼容的,以便将来扩展以支持所有monad。

是的,过去的反对意见是,如果我们为概念上类似于单子的事物添加do ,但是如果没有完全支持它们,那会让人们感到悲伤。

同时,虽然我们可能使它与完整的do-notation向前兼容,但我们可能不应该添加完整的do-notation。 它不能与if / while / for / loopbreak / continue / return ,我们必须能够catch (或本例中do )块内使用。 (这是因为完整的do表示法是根据高阶函数定义的,并且如果将catch块的内容填充到一系列嵌套的闭包中,则会突然控制流的所有中断。)

那么到底这个缺点的do是,它看起来像DO-符号,而不实际DO-符号,没有进展的良好路径成为DO-符号无论是。 就我个人而言,我对此完全满意,因为Rust仍然不会获得do标记-但这就是混乱。

@nikomatsakis ,#42526已合并,您可以将其标记为已在跟踪列表中完成:)

是否可以使catch关键字与上下文相关,但是如果范围为struct catch则将其禁用,并发出弃用警告?

不知道这是多么合适,但是我遇到了一个问题,也许这需要解决,因为有时您想中止一个ok而不是错误值,而当您经常想使用return ,这是当前可能的通过_outer_终止中止_inner_错误。 就像当函数say返回Option<Result<_,_>> ,这在迭代器中很常见。

特别是我现在在一个项目中有一个宏,我现在大量使用它:

macro-rules! option_try {
    ( $expr:expr ) => {
        match $expr {
            Ok(x)  => x,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

通常在Iterator::next实现中调用的函数在失败时需要立即中止Some(Err(e)) 。 该宏在普通函数体内起作用,但在catch块内不起作用,因为catch不能完全捕获return而只能捕获特殊的?语法。

尽管最后带标签的返回将使整个catch块想法变得多余,不是吗?

似乎#41414已完成。 有人可以更新OP吗?

更新:RFC rust-lang / rfcs#2388现在已合并,因此catch { .. }将替换为try { .. }
请参阅此评论上方的跟踪问题。

对于2015版,这意味着什么, do catch { .. }语法仍在走向稳定,还是会被丢弃,仅在2018+版中通过try { .. }支持?

@ Nemo157后者。

当前提案中是否有两个陈述try ... catch ... ? 如果是这样,我就没有语义。

无论如何,如果这个建议仅是关于废话的,那么我对此很满意。 即catch块是否会更改?运算符退出的位置?

当所有复选框都在顶部帖子中打勾时,我们什么时候才能前进? 如果仍然有未解决的问题,我们需要添加新的复选框。

可能还有许多未解决的未解决问题。
例如,ok包装行为未在lang团队中解决, Try的设计未最终确定,依此类推。 我们可能应该将此问题分成几个更有针对性的问题,因为它可能已经失效了。

嗯...这些问题没有记录下来让我感到困扰。

@ mark-im所以要澄清一下,我认为它们已经在某个地方了。 但不在一个位置; 在各种RFC和问题中,这有点分散,所以我们需要做的是将它们记录在正确的位置。

支持特性的设计在https://github.com/rust-lang/rust/issues/42327中进行了跟踪

因此,我认为这里只剩下try{} ,而我所知道的唯一分歧是在RFC中解决并在上述问题之一中再次确认的事情。 不过,最好有一个明确的跟踪问题。

我将为一个尚待执行的未完成的实现任务添加一个复选框。

@scottmcm我知道@joshtriplett对OK包装有担心(在try RFC中有说明),我个人想在最初稳定try { .. }限制break ,所以你不能做loop { try { break } }类的事情。

@Centril

这样你就不能做loop { try { break } }

现在,您不能在非循环块中使用break ,这是正确的: break仅应在循环中使用。 要提早离开try块,标准方法是编写Err(e)? 。 并迫使早生叶片始终处于“异常”控制路径中。

所以我的建议是应该允许您显示的代码,并且应该打破loop ,而不仅仅是离开try

直接的好处是,当您看到break您知道它会从循环中中断,您可以随时用continue替换它。 同样,它消除了在loop使用try块而您要退出循环时,必须标记断点的需要。

@Centril感谢您提出这些建议。

关于break ,我个人可以简单地说try不在乎break并将其传递到包含循环。 我只是根本不希望breaktry进行交互。

至于Ok包裹,是的,我想在稳定try之前解决这个问题。

@centril是的,我知道。 但要记住,这是重新提出这个问题是非常重要的。 RFC决定拥有它,没有它就实现了,但是最初的意图是_again_ ,并且实现更改为遵循RFC。 因此,我的主要问题是是否有任何重大事实发生了变化,特别是考虑到这是我在RFCs + IRLO上讨论过的最嘈杂的话题之一。

@scottmcm当然,如您所知,我同意保留Ok -wrapping;),并且我同意应考虑解决该问题。

我只是想对此发表评论,不确定这是否正确:

本质上,我遇到的情况是GUI框架中的回调-而不是返回OptionResult ,他们需要返回UpdateScreen ,以告诉框架屏幕是否是否需要更新。 通常,我根本不需要日志记录(登录每个次要错误根本不可行),并且在发生错误时仅返回UpdateScreen::DontRedraw 。 但是,使用当前的?运算符,我必须始终编写以下代码:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

由于我无法通过Try运算符将Result::Err转换为UpdateScreen::DontRedraw ,因此这变得非常乏味-通常我在哈希映射中进行简单的查找就会失败(这不是错误) )-经常在一个回调中使用?运算符5-10次。 因为上面写的很冗长,所以我当前的解决方案是像这样impl From<Result<T>> for UpdateScreen ,然后在回调中使用一个内部函数,如下所示:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }

    callback_inner(data).into()
}

由于回调函数用作函数指针,因此我不能使用-> impl Into<UpdateScreen> (由于某种原因,函数指针当前不允许返回impl )。 因此,对我而言,唯一使用Try运算符的唯一方法是执行内部函数技巧。 如果我可以简单地执行以下操作,那就太好了:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

我不确定当前的提案是否可以实现,只是想添加我的用例进行考虑。 如果自定义的Try运算符可以支持类似的功能,那就太好了。

编辑:
我犯了一个错误。


忽略此帖子


可以在类型推断中更好地发挥作用,即使在简单情况下也会失败。

fn test_try(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x // : Option<_> // why is this type annotation necessary
    = try { div? + 1 };

    println!("{:?}", x);
}

如果将其重写为使用闭包而不是try块(并且在此过程中松散自动包装),那么我们得到

fn test_closure(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x =  (|| (div? + 1).into())();

    println!("{:?}", x);
}

不需要类型注释,但确实需要我们包装结果。

操场

@KrishnaSannasi基于闭包的示例也具有类型推断失败(操场),因为Into不会限制输出,并且以后也不会在任何地方使用它。

这似乎是Try特性而不是try块的问题,类似于Into它不会从输入到输出传播任何类型信息,因此输出类型必须由以后的使用情况确定。 在https://github.com/rust-lang/rust/issues/42327上有关于该特征的讨论,我还没有读过,所以我不确定那里是否有任何建议可以解决这个问题。

N

是的,我在最后一刻更改了我的代码,以使其被使用,并且没有对其进行测试。 我的错。

我们离稳定尝试块还有多远? 这是我每晚所需的唯一功能:D

Ar

我相信,一旦做到这一点,就可以稳定下来。

阻止try {} catch(或其他后续标识)以留出将来的设计空间,并向人们指出如何通过match来做自己想做的事情

现在没有中间层设计允许该功能,同时仍然允许为将来保留设计空间(并因此最终成为catch块)吗?

我进行的PR仍应选中该框,CC @nikomatsakis

昨天我第一次尝试使用它,对此我感到有些惊讶:

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    }?;
    Ok(x)
}

由于以下原因而无法编译

error[E0284]: type annotations required: cannot resolve `<_ as std::ops::Try>::Ok == _`

相反,我必须做

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: Result<(), ()> = try {
        Err(())?
    };
    let x = x?;
    Ok(x)
}

代替。

起初这令人困惑,因此也许值得更改错误消息或在--explain提及?

如果您将第一个示例中的问号向下移至

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    };
    Ok(x?)
}

您会收到更好的错误消息。 由于Rust无法确定将try { ... }解析为哪种类型,因此出现错误。 因为它不能解析此类型,所以它不知道<_ as Try>::Ok类型是什么,这就是为什么会出现错误的原因。 (因为?运算符将取消包装Try类型并返回Try::Ok类型)。 Rust无法单独使用Try::Ok类型,必须通过Try特性和实现该特性的类型来解决。 (这是当前类型检查的局限性)

该功能的所有功能都已实现,对吗? 如果是这样,在稳定之前我们要坐多久?

我认为这是否是我们想要的仍然是一个悬而未决的问题。 特别是,这里有一些关于我们是否要使用异常语言(try,catch)的讨论。

就个人而言,我强烈反对试图给人一种Rust带有异常之类的印象。 我认为特别是使用catch这个词是一个坏主意,因为任何来自具有例外情况的语言的人都会认为这确实可以解决,而事实并非如此。 我希望它会让人感到困惑和痛苦。

特别是,这里有一些关于我们是否要使用异常语言(try,catch)的讨论。

我认为https://github.com/rust-lang/rfcs/pull/2388明确解决了是否可以接受try作为名称。 这不是一个公开的问题。 但是Try特性以及Ok -wrapping的定义似乎是正确的。

Ok -wrapping已在原始RFC中确定,然后在实现过程中删除,最后在以后重新添加。 我不知道这是一个悬而未决的问题。

@rpjohnst好吧,这是因为Josh不赞成原始RFC的决定... :)对我来说这是一个已解决的问题。 见https://github.com/rust-lang/rust/issues/31436#issuecomment -427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment -427252202和https://开头github.com/rust-lang/rust/issues/31436#issuecomment -437129491。 无论如何……我的评论的重点是,作为“例外语言”的try已经解决。

哇,什么时候发生的? 我记得的最后一件事是内部讨论。 我也非常反对Ok-wrapping :(

真是的不敢相信这发生了。 Ok -wrapping太可怕了(它打破了非常明智的直觉,即函数中的所有返回表达式都应为函数的返回类型)。 是的,绝对可以使用@ mark-im。 乔希的分歧足以使这个问题成为公开问题,并引起更多讨论吗? 我很乐意为他提供支持,以对抗这一问题,而不是说这意味着作为非团队成员。

Ok在RFC 243中接受的包装(实际上是定义?运算符的包装,如果您想知道何时发生的话)不会改变函数返回表达式的类型。 这是RFC 243的定义方式: https :

该RFC还引入了表达式形式catch {..} ,用于“作用域” ?运算符。 catch运算符执行其关联的块。 如果未引发异常,则结果为Ok(v) ,其中v是块的值。 否则,如果引发异常,则结果为Err(e)

请注意, catch { foo()? }本质上等效于foo()

也就是说,它需要一个类型T的块,并无条件地对其进行包装以产生一个类型Result<T, _> 。 块中的任何return语句都完全不受影响; 如果该块是函数的结尾表达式,则该函数必须返回Result<T, _>

每天晚上都可以通过这种方式实现: https= nightly = debug = 2018 = 88379a1607d952d4eae1d06394b50959。 这项工作是由lang小组在此线程上进行大量讨论并链接到该线程之后进行的:rust-lang / rust#41414(这也链接在本期顶部)。

PDT于2019年5月28日下午5:48:27,Alexander Regueiro [email protected]写道:

真是的不敢相信这发生了。 Ok包装太恐怖了(它
打破了非常明智的直觉,即所有返回表达式都以
函数应为函数的返回类型)。 是的,绝对
在此使用@ mark-im。 乔希的分歧足以使这成为
未解决的问题,并获得更多讨论? 我很乐意支持他
在与之抗争中,并不是说作为非团队成员就意味着某种意义。

谢谢。 我不仅只是为了我自己而不同意; 我也代表许多见过的人表达与我相同的立场。

@joshtriplett @ mark-im @alexreg

你们中的一个可以解释为什么您觉得Ok包装太令人讨厌吗或提供指向以前已经解释过的地方的链接吗? 我去看了一下,但是在粗略的视图中我什么都没看到。 我没有这方面的知识(我很少评论,因为我看到所有复选框都选中了,而且一个月都没有讨论),但是现在我踢了这个大黄蜂的巢,我想更好地理解这些论点。

罗素·约翰斯顿在2019年5月28日星期二03:40:47 PM -0700写道:

Ok -wrapping已在原始RFC中确定,然后在实现过程中删除,最后在以后重新添加。 我不知道这是一个悬而未决的问题。

我认为您部分回答了自己的问题。 我不认为每个人
原始RFC讨论中涉及的内容在同一页面上; try
绝对是许多人想要的东西,但是没有达成共识
好的包装。

在2019年5月28日星期二下午07:44:46 PM,Mazdak Farrokhzad写道:

无论如何……我的评论的重点是,作为“例外语言”的try已经解决。

作为澄清,我发现“例外”的隐喻没有吸引力,
而且许多尝试使用try-fn和Ok-wrapping的尝试似乎
试图使语言具有类似异常机制的伪造。
但是try本身,作为捕获?而不是
函数边界,作为控制流构造是有意义的。

加布里埃尔·史密斯(Gabriel Smith)在2019年5月28日星期二晚上11:37:33 PM写道:

谁能解释一下为什么您觉得Ok包装如此令人讨厌

作为以下几个原因之一:

在2019年5月28日,星期二,00-07:05:48:27,亚历山大·里格罗(Alexander Regueiro)写道:

它打破了非常明智的直觉,即函数中的所有返回表达式都应为函数的返回类型

这打破了人们用于类型导向推理的各种方法
关于功能和代码结构。

我在这里当然有自己的想法,但是我们现在不可以重新打开这个话题吗? 我们刚刚就语法问题进行了超过500次的有争议的对话,因此我想暂时避开地雷。

如果在讨论该问题的lang小组中被阻止,则应再次取消选中“解决catch块是否应该“包装”结果值(#41414)”复选框(可能会被lang小组评论为已阻止),以便人们这个跟踪问题知道状态吗?

抱歉,我没有尝试重新打开任何内容-只是重述跟踪问题中确定的标记内容以及发生的时间和方式。

@rpjohnst感谢您提供信息!

@yodaldevoid Josh几乎总结了我的想法。

我不太反对将ok封装限制在一个块中(而不是影响一个函数的类型),但是我认为它仍然设置了一个不好的先例:正如Josh所说的:“我没有发现吸引人的异常隐喻”

@joshtriplett也从本质上总结了我的观点:问题是“异常”隐喻的适用性(可以说是恐慌+ catch_unwind更加荒谬)和基于类型的推理。 我确实也可以将try块用作作用域和控制流机制,但不是更激进的观点。

好吧,很公平,让我们不在这里进行整个辩论...也许只是按照建议取消选中该框,然后使用该线程中提到的一些基本原理(在自己的时间)放回lang-team辩论? 只要不趋于稳定,我想这听起来是合理的。

是否已同意类型注释的语法? 我希望有一些try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?; ,对编译甚至不感兴趣: error[E0282]: type annotations needed

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4e60d44a8f960cf03307a809e1a3b5f2

我前一阵子读了这些评论(然后在github上再次加载300条评论太繁琐了),但是我确实记得关于Try::Ok包装辩论的大多数(如果不是全部)示例都使用了Ok在示例中为Option也会实现Try ,我想知道这如何影响团队在辩论的哪一方的立场。

每次使用Rust时,我都会一直在思考“老兄,我真的希望我可以在这里使用try块”,但是大约30%的时间是因为我真的希望我可以在Option s中使用try(例如我曾经在Scala中使用过,它使用for语法将其应用于一般的monad,但是与try非常相似)。

就在今天,我使用的是json板箱,它公开了返回选项的as_*方法。

使用这两种语法,我的示例将是:

match s {
  "^=" => |a, b| try { a.as_str()?.starts_with(b.as_str()?) }.unwrap_or(false),
  "$=" => |a, b| try { Some(a.as_str()?.ends_with(b.as_str()?)) }.unwrap_or(false),
  // original
  "$=" => |a, b| {
    a.as_str()
      .and_then(|a| b.as_str().map(|b| (a, b)))
      .map(|(a, b)| a.starts_with(b))
      .unwrap_or(false)
    },
}

我认为,就上下文而言,返回类型是Option还是Result是很清楚的,而且,这实际上并不重要(就代码理解而言)。 透明地,含义很明确:“我需要检查这两项是否有效并对其进行操作。” 如果我必须选择其中一个,那么我会选择第一个,因为当您考虑将此功能嵌入更大的上下文时,我不认为会失去任何理解,因为try一直是。

刚开始查看该线程时,我反对Ok换行,因为我认为最好是明确的,但是从那时起,我开始关注我说“我希望我能可以在此处使用try块”,我得出的结论是, Ok包裹好。

我原本以为,如果您的最后一条语句是一个返回实现Try的类型的函数,那么Ok包装不是更好的选择,但是语法上的区别是

try {
  fallible_fn()
}

try {
  fallible_fn()?
}

在这种情况下,我再次认为Ok -wrapping更好,因为它清楚地表明fallible_fnTry返回函数,因此它实际上更加明确。

我想知道反对派对此的看法,并且由于在该线程中看不到其他人,因此@joshtriplett。

编辑:我应该提一下,我只是从人机工程学/阅读理解的角度来看待这个问题。 我不知道一个人在实现方面是否比另一个更具有技术优势,例如更容易推断。

我还想给try进行一些嵌套的Option解析:

#![feature(try_blocks)]

struct Config {
    log: Option<LogConfig>,
}

struct LogConfig {
    level: Option<String>,
}

fn example(config: &Config) {
    let x: &str = try { config.log?.level? }.unwrap_or("foo");
}

这失败了

error[E0282]: type annotations needed
  --> src/lib.rs:12:19
   |
12 |     let x: &str = try { config.log?.level? }.unwrap_or("foo");
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

我最近的是

fn example(config: &Config) {
    let x: Option<&str> = try { &**config.log.as_ref()?.level.as_ref()? };
    let x = x.unwrap_or("foo");
}

as_ref非常不幸。 我知道Option::deref会在这里有所帮助,但还不够。 这感觉就像匹配人体工程学(或相关概念)应发挥作用。

多行也是不幸的。

像整数文字一样, try使用Result的推断回退吗? 那会让@shepmaster的第一次尝试推断Result<&str, NoneError>吗? 还有哪些尚待解决的问题-可能会找到要转换为?的常见错误类型? (我是否错过了某个地方的讨论?)

@shepmaster我同意类型推断。 不过,很有趣的是,我使用了一些幼稚的try_实现尝试了您的确切代码,并且效果很好: https :

    let x = try_! { config.log?.level? }.unwrap_or("foo".to_owned());
    assert_eq!(x, "debug");

效果很好。

有点天真的try_实现

是的,但是您的宏调用返回String ,而不是&str ,需要所有权。 您没有显示周围的代码,但这将失败,因为我们没有Config所有权:

fn example(config: &Config) {
    let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
}
error[E0507]: cannot move out of captured variable in an `Fn` closure
  --> src/lib.rs:20:21
   |
19 | fn example(config: &Config) {
   |            ------ captured outer variable
20 |     let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
   |                     ^^^^^^^^^^ cannot move out of captured variable in an `Fn` closure

它还无条件地分配String ; 在此示例中,我使用了unwrap_or_else来避免效率低下。

可惜的是,此功能在async/await块之前未稳定。 海事组织,拥有它会更加一致

let fut = async try {
    fut1().await?;
    fut2().await?;
    Ok(())
};

而不是让它在没有try 。 但是我想那艘船已经航行很久了。

回复:自动包装,我认为现在不可能与async块保持一致。 async块将某种形式的“自动包装”为实现Future的匿名类型。 但是,即使提前返回也是如此,这对于try块是不可能的。

如果我们确实有一个假想的async try块,可能会变得更加令人困惑。 如果自动换行的结果呢?

我认为我们在自动包装方面没有失去任何一致性的机会。 async块和函数都可以使用? ,并且两者都必须进行自己的手动Ok -wrapping处理,以实现早期和最终返回。

另一方面, try块可以将?与自动Ok换行,包括“早退”,假设有早退功能-可能是标签断裂-价值。 假设的try函数可以轻松地对早期和最终收益自动进行Ok包装。

假设的async try块可以简单地组合两个功能-自动Ok换行,然后自动Future换行。 (另一种方法是不可能实现的,可以说是try async 。)

我确实看到的不一致之处是我们已经将async块与函数进行了混合。 (这发生在最后一分钟违背RFC,不会少。)这是什么意思是, returnasync块退出块,而returntry块退出包含函数。 但是,这些至少在隔离上确实有意义,并且没有提早返回标签破缺值的async块将很难使用。

阻止这种稳定的任何措施,还是没有人花时间去做呢? 我有兴趣创建必要的PR,否则🙂

在太平洋标准时间(PST)2019年11月18日2:03:36,Kampfkarren [email protected]写道:

阻止这种稳定的任何措施,或者没有人采取措施
该做什么了? 我对创建必要的PR感兴趣
否则🙂>
>
->
您收到此邮件是因为有人提到您。>
直接回复此电子邮件或在GitHub上查看:>
https://github.com/rust-lang/rust/issues/31436#issuecomment -554944079

是的,稳定剂正在通过Ok-wrapping的决策来进行。 在我们就其行为方式达成共识之前,不应稳定这一点。

我个人反对Ok-wrapping,但我想知道事后添加将有多困难。 ok-wrapping向前兼容吗?

我可以想象一些棘手的情况,例如Result<Result<T,E>>歧义,但是在这种模棱两可的情况下,我们可能会退回到无包装的情况。 然后,用户可以明确地确定包装以消除歧义。 这似乎还不错,因为我不希望这种含糊之处经常出现。

完全应该没有歧义,因为它不是Ok -coercion而是Ok -wrapping。 try { ...; x }将产生Ok(x)就像明确为Ok({ ...; x })

@joshtriplett这是未解决的吗? 跟踪问题已将resolve whether catch blocks should "wrap" result value选中,引用了https://github.com/rust-lang/rust/issues/41414

@rpjohnst对不起,我应该更加清楚。 我的意思是,如果我们现在稳定try而没有Ok-wrapping,我相信以后可以向后兼容地添加它。

也就是说,我认为大多数人都同意我们应该有try块,但并不是每个人都同意catch或OK-wrapping。 但是我认为这些讨论不需要阻止try ...

@Kampfkarren是的。 上面的对话详细介绍了此事的进展。 在没有充分咨询所有人的情况下过早地将其打勾。 @joshtriplett特别担心,其他几个人(包括我自己)也对此表示担忧。

@ mark-im您如何看待将来会添加Ok-wrapping? 我试图弄清楚该怎么做,我不太清楚。

因此,我将通过说不知道这是一个好主意来作为开头...

我们将稳定try块而无需确定包装。 例如:

let x: Result<usize, E> = try { 3 }; // Error: expected Result, found usize
let x: Result<usize, E> = try { Ok(3) }; // Ok (no pun intended)

后来,假设我们就应该进行Ok-wrapping达成共识,那么我们可以允许某些以前无法使用的情况:

let x: Result<usize, E> = try { 3 }; // Ok
let x: Result<usize, E> = try { Ok(3) }; // Also Ok for backwards compat
let x: Result<Result<usize, E1>, E2> = try { Ok(3) }; // Ok(Ok(3))
let x: Result<Result<usize, E1>, E2> = try { Ok(Ok(3)) }; // Ok(Ok(3))

问题是,这是否会导致某些事情变得比以前更加模棱两可。 例如:

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

虽然,也许Ok-wrapping已经必须解决这个问题?

无论如何,我的直觉是,这种奇怪的情况不会经常出现,因此可能没什么大不了的。

除了返回类型为ResultOption之外,使用Ok-wrapping怎么样? 在大多数情况下,这将允许使用更简单的代码,但可以在需要的地方指定确切的值。

// Ok-wrapped
let v: Result<i32, _> = try { 1 };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Ok(1) };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Err("error") };

// Ok-wrapped
let v: Option<i32> = try { 1 };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { Some(1) };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { None };

添加Ok -coercion或某种与语法相关的Ok -wrapping(这是为了在没有稳定的情况下支持稳定化并在以后引入)会很不利于可读性,并且在i.rl.o上已被广泛反对(通常是人们误解了实现的直接Ok -wrapping)。

我个人强烈支持Ok -wrapping的实现,但是更强烈地反对任何形式的强制性或语法依赖性,这会使理解难以包裹的情况(我不得不写无用的Ok(...)到处都是,必须尝试弄清楚它是否被强制执行)。

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

虽然,也许Ok-wrapping已经必须解决这个问题?

不会,这无疑是Ok(Err(3))Ok -wrapping与语法或类型无关,它只包装Try::Ok变体中的块输出。

@ mark-im我认为稳定后我们不能合理地从一个转移到另一个。 正如我认为Ok-wrapping那样糟糕,不一致的Ok-wrapping试图猜测您是否想要它会更糟。

在我的代码库中,我正在处理许多可选值,因此很久以前,我就引入了自己的try块,例如macro。 在我介绍它时,有和没有Ok-wrapping的各种变体,而Ok-wrapping版本的结果更加符合人体工程学,这是我最终使用的唯一宏。

我有很多需要使用的可选值,而且它们大多是数字的,因此我有很多这样的情况:

let c = try { 2 * a? + b? };

如果没有Ok-wrapping,那么与使用真正的try块相比,我可能会停留在自己的宏上,因此与人体工程学相比要少得多。

鉴于此跟踪问题的历史悠久,与?运算符的原始和令人遗憾的混淆以及Ok包装问题的障碍,我建议彻底关闭此问题并发送try回到RFC流程的开始,在此讨论可以得到应有的可见性,并(有希望)得出某种结论。

如果没有Ok-wrapping,这将不符合人体工程学

您能否详细说明它将引入哪些非人机工程学内容?

如果没有Ok-wrapping,您的示例将如下所示:

let c = try { Ok(2 * a? + b?) };

我认为这非常好。

我的意思是,对于一个像这样的小示例,它看起来可能有点过大,但是try块包含的代码越多,此Ok(...)包装程序所造成的影响就越小。

除了@CreepySkeleton的评论,应该注意的是,如果try块不执行此操作,则可以很容易地创建一个模拟Ok -wrapping的宏(而且肯定会创建一个宏)这个小宏的标准板条箱),但事实并非如此。

Try特性不稳定时,该宏是不可能的。

为什么? 无论如何,当它确实稳定时(假设将来不会太远),这将很有可能。

@ Nemo157 try区块现在也仅在每晚运行,在我们决定淘汰Try极少数情况下,它们很可能无法稳定。 这意味着在Try之前它们可能不会稳定下来。 因此,说不可能是不可能的。

@KrishnaSannasi我很好奇为什么可能会撕掉Try

@ mark-im我认为不会,我只是在解释为什么担心Try在夜间wrt尝试块不是现实的问题。 我期待Try稳定下来。

鉴于?已被稳定, try块有一个明确的设计既包括ResultOption以同样的方式, ? ,确实没有理由阻止Try稳定它们。 我没有一直密切关注它,但是我的印象是,对于Try的设计,其共识要比try块少得多,因此我可以看到tryTry特性之前的几年内保持稳定(就像? )。 即使Try特性被放弃,我也没有理由阻止try块被稳定为只使用ResultOption?将会是。

(对于_why_,您无法通过给出稳定的try块和不稳定的Try特征来编写该宏,该宏将扩展为try { Try::from_ok($expr) } ;您可以创建每个类型的宏只需ResultOption ,但IMO不会达到“非常容易模仿”的地步)。

鉴于?已经是特殊情况的稳定对象,即使Try特性不能在稳定对象上使用,我也看不到为什么Try不稳定会阻止try块被在稳定器上实现,因为如果删除了Try特质,我们仍然具有在稳定器上支持? Option和Result,只是没有人机工程学。

我建议尝试捕获语义的以下概念...

考虑以下代码:

union SomeFunctionMultipleError {
    err0: Error1,
    err1: Error2,
}

struct SomeFunctionFnError {
    index: u32,
    errors: SomeFunctionMultipleError,
}

fn some_function() -> Result<i32, SomeFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(SomeFunctionFnError{ index: 0, errors: SomeFunctionMultipleError {err1: Error2 {id0: 0, id1: 0, id3: 0}}})
    }
}

union OtherFunctionMultipleError {
    err0: Error1,
    err1: Error2,
    err2: Error3,
}

struct OtherFunctionFnError {
    id: u32,
    errors: OtherFunctionMultipleError,
}

fn other_function() -> Result<i32, OtherFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(OtherFunctionFnError {id: 0, errors: OtherFunctionMultipleError {err0: Error1 {id0: 0, id1: 0}}})
    }
}

这是可以由Rust中具有以下语法功能的零开销异常生成的代码:

fn some_function() -> i32 throws Error1, Error2 {
    if 0 == 0 {
        2
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function() -> i32 throws Error1, Error2, Error3 {
    if 0 == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

甚至这些错误都可以由编译器隐式推导:

fn some_function(i: i32) -> i32 throws { // Implicitly throws Error1, Error2
    if i == 0 {
        2
    } else if i == 1 {
        Error1 {id0: 0, id1: 0, id3: 0}.throw
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function(i: i32) -> i32 throws { // Implicitly throws Error1
    if i == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

这没别的语法糖! 行为是一样的!

大家好,

有没有人看过我关于上述零开销例外的建议?

@redradist try块的要点之一是我们可以在函数内部使用它,而无需为每个块创建一个函数。 就我所知,您的建议在这里完全无关。

就在今天,我感到需要try块。 我有一个大型函数,其中有许多?操作。 我想为错误添加上下文,但是对于每个?这样做将需要大量的样板。 使用try捕获错误并将上下文添加到一个位置可以避免这种情况。

顺便说一句。 在这种情况下,将操作包装在内部函数中会很困难,因为上下文没有明显的生存期,将内容划分为多个函数会破坏NLL。

我想提一下,在当前实现中, try块不是表达式。 认为这是一个疏忽。

我想提一下,在当前实现中,try块不是表达式。 认为这是一个疏忽。

您能发布不适合您的代码吗? 我可以在这里的表达式上下文中使用它:( Rust Playground

#![feature(try_blocks)]

fn main() {
    let s: Result<(), ()> = try { () };
}

当然可以

这是另一个表明对try块的类型推断尚未完成的情况。 这尤其令人讨厌,因为if let块中不支持类型说明。

@ Nokel81您前面的示例的问题在于if let $pat = $expr中的表达式不是正则表达式上下文,而是特殊的“无括号表达式”上下文。 对于这个作品与结构表现如何,看一个例子这个例子它在语法上是明确的,有一个结构的表达那里,这个例子在那里是没有的。 因此,错误并非不是try不是表达式,而是错误是错误的,应该说“此处不允许try表达式;尝试用括号将其括起来”(以及取消了有关不必要的括号的错误警告)。

您后面的示例对于类型推断实际上是模棱两可的。 在这种情况下, e_: From<usize> ,这是不足以提供具体类型的信息。 您需要使用某种方式为它提供一个具体的类型,以允许类型推断成功。 这不是特定于try ; 这就是Rust中类型推断的工作方式。

现在,如果您立即尝试匹配为Ok并丢弃Err大小写,那么您会遇到一个次优错误消息的情况,但没有真正简单的解决方法

非常感谢您的深入解释。 我猜我仍然感到困惑,为什么后来对于类型推断变得模棱两可。 为什么表达式的类型不是Result<isize, usize>

?运算符可以使用From特性在不同的错误类型之间执行类型转换。 它大致扩展为以下rust代码(忽略Try特性):

match expr {
    Ok(v) => v,
    Err(e) => return From::from(e),
}

From调用使用最终返回表达式的类型来确定要执行的错误类型转换,并且不会默认为自动传递值的类型。

抱歉,如果此问题已得到解决,但对我来说奇怪的是:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result = try { // no type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

无法编译为:

error[E0282]: type annotations needed

但:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result : Result<_, _> = try { // partial type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

没关系。

如果这是一个问题,因为我无法理解Result的类型参数,但是如上所述,情况并非如此,一旦被告知rust的结果,rustc就可以执行推理try表达式是Result某种形式,应该可以从core::ops::Try::into_result推断出来。

有什么想法吗?

@nwsharp这是因为try / ?Try类型的泛型。 如果您还有其他类型的impl Try<Ok=_, Error=()> ,则try块可以评估为该类型以及Result 。 已废止,您的示例大致

#![feature(try_trait, label_break_value)]

use std::ops::Try;

fn main() -> Result<(), ()> {
    let result /*: Result<_, _>*/ = 'block: {
        match Try::into_result(Err(())) {
            Ok(ok) => Try::from_ok(ok),
            Err(err) => {
                break 'block Try::from_error(From::from(err));
            }
        }
    };
    result.map_err(|err| err)
}

@ CAD97谢谢您的解释。

就是说,我没想到try能够有效地在不同的Try impls之间造成某种转换。

期望去surgaring其中相同的Try IMPL选择为into_resultfrom_okfrom_error

在我看来,人体工程学上无法执行类型推断的损失(尤其是考虑到甚至没有替代的impl Try存在),并没有超过允许这种转换的好处。

我们可以通过消除歧义来允许推理,并通过以下方式保持选择加入转换的能力:

try { ... }.into()

带有相应的毯子暗示:

impl<T: Try, E: Into<T::Err>> From<Result<T::Ok, E>> for T {
    fn from(result: Result<T::Ok, E>) -> Self {
        match result {
            Ok(ok) => T::from_ok(ok),
            Err(err) => T::from_err(err.into()),
        }
    }
}

(说实话,我认为不管是否有错,尽管我个人对此表示怀疑,但我会怀疑错误类型的自动转换。如果需要,用户应该在.map_err()上使用Result 。)

总的来说,我认为这种脱糖是“太聪明了”。 它隐藏得太多,其当前的语义易于使人们感到困惑。 (特别考虑到当前实现要求在不直接支持它们的内容上进行类型注释!)

或者,我想进一步深入探讨。

impl <T: Try, U: Try> From<U> for T 
    where U::Ok : Into<T::Ok>, U::Err : Into<T::Err>
{
    fn from(other: U) -> Self {
        match other.into_result() {
            Ok(ok) => Self::from_ok(ok.into()),
            Err(err) => Self::from_err(err.into()),
        }
    }
}

管他呢...

就是说,我没想到try能够有效地导致不同Try impls之间的某种转换。

我希望能对into_resultfrom_okfrom_error选择相同的Try impl去杂音。

在我看来,人体工程学上无法执行类型推断的损失(特别是考虑到甚至没有替代impl Try存在),并没有超过允许这种转换的好处。

有四种稳定的Try类型: Option<T>Result<T, E>Poll<Result<T, E>>Poll<Option<Result<T, E>>

NoneError不稳定,因此Option<T>停留在Option<T>尝试,而NoneError不稳定。 (请注意,尽管文档明确指出From<NoneError>为“为错误类型启用option? 。”)

但是, Poll会将错误类型设置为E 。 因此, Try的“类型变形”是稳定的,因为您可以在-> Result<_, E> ?Poll<Result<T, E>>中获得Poll<T>并尽早返回E案例。

实际上,这为“可爱”的小助手提供了动力:

fn lift_err<T, E>(x: Poll<Result<T, E>>) -> Result<Poll<T>, E> { Ok(x?) }

@ CAD97感谢您的

是否考虑过允许指定所需的impl Try来缓解此处的不直观行为?

例如,稍微骑自行车, try<T> { ... } 。 还是还有什么可以继续的呢?

为了在此处添加更多颜色,一堆Resulttry { }不能“仅仅”产生Result的事实是出乎意料的,这让我很难过。 我理解为什么,但我不喜欢它。

是的,已经讨论了“通用类型归因”(您要搜索的术语)和try 。 我想,我最后听到, try: Result<_, _> { .. }最终打算工作。

但是,我确实同意您的意见: try块应该进行一些有针对性的诊断,以确保指定了它们的输出类型。

请参阅此单独的问题,以解决一个狭窄的特定问题,以解决语言团队在Ok包装问题上的共识。

在发表评论之前,请先阅读该主题的开头评论,特别是请注意,该主题仅与一个问题有关,与与try?Try相关的任何其他问题无关

我不明白为什么需要try块。 这个语法

fn main() -> Result<(), ()> {
    try {
        if foo() {
            Err(())?
        }
        ()
    }
}

可以替换为:

fn main() -> Result<(), ()> {
    Ok({
        if foo() {
            Err(())?
        }
        ()
    })
}

它们都使用相同数量的字符,但是第二个已经稳定。

将结果分配给变量时,这可能表明应创建一个辅助函数以返回结果。 如果不可能,可以使用闭包代替。

@dylni try块在不包含函数整体时特别有用。 错误的?运算符使流控制转到最里面的try块的末尾,而无需从函数中返回。


fn main()/ *此处无结果* / {
让结果=尝试{
foo()?. bar()?. baz()?
};
匹配结果{
//…
}
}

@SimonSapin经常这样吗? 我很少遇到过这样的情况,通常有一种很好的解决方法。 在您的示例中:

fn main() /* no result here */ {
    let result  = foo()
        .and_then(|x| x.bar())
        .and_then(|x| x.baz());
    match result {
        // …
    }
}

这比较冗长,但是我认为一个更简单的解决方案是方法关闭语法:

fn main() /* no result here */ {
    let result  = foo()
        .and_then(::bar)
        .and_then(::baz);
    match result {
        // …
    }
}

还可以使用and_then正确推断类型,在这里您需要try类型注释。 我很少遇到这种情况,以至于我认为Terser语法不值得在可读性上造成伤害。

接受的RFC还有其他原因: https :

无论如何,赞成使用? (和.await )运算符而不是像and_then这样的链接方法进行控制流的语言构造的参数已经被广泛讨论。

无论如何,赞成使用? (和.await )运算符而不是像and_then这样的链接方法进行控制流的语言构造的参数已经被广泛讨论。

@SimonSapin谢谢。 这并重新阅读RFC,使我确信这很有用。

我以为我可以使用try块轻松地将上下文添加到错误中,但是到目前为止还没有运气。

我写了一个很好的小功能。 请注意, File::open()?失败,并带有std::io::Error而以下行失败,并出现了anyhow::Error 。 尽管类型不同,但编译器会找出如何将两者都转换为Result<_, anyhow::Error>

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> Result<(usize, usize), anyhow::Error> {
    let path = path.as_ref();
    let mut file = BufReader::new(File::open(path)?);
    Ok(config.root_store.add_pem_file(&mut file)
        .map_err(|_| anyhow!("Bad PEM file"))?)
}

我想添加一些错误上下文,因此尝试使用try块以及with_context()

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> anyhow::Result<(usize, usize)> {
    let path = path.as_ref();
    try {
        let mut file = BufReader::new(File::open(path)?);
        Ok(config.root_store.add_pem_file(&mut file)
            .map_err(|_| anyhow!("Bad PEM file"))?)
    }
    .with_context(|| format!("Error adding certificate {}", path.display()))
}

但是现在类型推断失败了:

error[E0282]: type annotations needed
  --> src/net.rs:29:5
   |
29 | /     try {
30 | |         let mut file = BufReader::new(File::open(path)?);
31 | |         Ok(config.root_store.add_pem_file(&mut file)
32 | |             .map_err(|_| anyhow!("Bad PEM file"))?)
33 | |     }
   | |_____^ cannot infer type
   |
   = note: type must be known at this point
   ```

I don't understand why a type annotation is needed here but not in the first case. Nor do I see any easy way to add one, as opposed to using an [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) which does let me add an annotation:

```rust
(|| -> Result<_, anyhow::Error> {
    let domain = DNSNameRef::try_from_ascii_str(host)?;
    let tcp = TcpStream::connect(&(host, port)).await?;

    Ok(tls.connect(domain, tcp).await?)
})()
.with_context(|| format!("Error connecting to {}:{}", host, port))

@jkugelman

再次,

这是因为try / ?Try类型的泛型。 如果您还有其他类型[[ impl Try<Ok=_, Error=anyhow::Error> ],则try块可以评估为该类型,也可以评估为Result

(另外,你不需要Ok在您的尾随表达式try块(#70941)。)

我认为这种情况继续出现,这意味着

  • 在稳定之前, try需要支持类型说明( try: Result<_,_> {或其他),否则可以减轻此问题,
  • try块的类型推断失败时,这肯定需要有针对性的诊断,并且
  • 在没有其他限制的情况下,我们应该强烈考虑给try类型回退到Result<_,_> 。 是的,这很困难,指定不足且可能有问题,但是它会解决try块的80%情况需要类型注释的问题,因为$12: Try<Ok=$5, Error=$8>不够具体。

此外,鉴于#70941似乎可以解决“是的,我们想要(某种形式的)' Try::from_ok包装'”,我们可能还想针对性地诊断try的尾部表达式。当x可以工作时, try块将返回Ok(x)

我怀疑尝试的正确行为是

  • 扩展语法以允许手动命名,例如try: Result<_, _> { .. }try as Result<>或其他(我认为try: Result可能很好吗?这似乎是首选语法)
  • 检查来自上下文的“期望类型”-如果存在,则最好将其作为try的结果类型
  • 否则,默认为Result<_, _> -这不是像i32那样的类型推断回退,它会更早发生,但这意味着像try { }.with_context(...)这样的事情可以编译。

但是,我担心,至少在未指定错误类型的情况下,我们可能会在?into强制附近出现错误。 特别是如果您在try块的结果中的?编写代码,如下所示:

#![feature(try_blocks)]

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x: Result<_, _> = try {
        std::fs::File::open("foo")?;
    };

    x?;

    Ok(())
}

fn main() { 
}

您仍然会收到错误(操场),这是正确的,因为尚不清楚应在哪个?上触发“强制”强制。

我不确定这里最好的解决方案是什么,但它可能涉及某种类型的推断回退,这会使我感到紧张。

可能涉及一些类型推断回退,这会让我感到紧张。

最简单的一种:如果在给定的Try块中对?所有使用都包含相同的Try::Error类型,则将该错误类型用于包含的try块(除非否则绑定)。

“(除非另有约束)”当然是微妙的令人恐惧的部分。

我希望我对这篇文章不要太过随意。 但是,我想将@nikomatsakis示例与并行世界中的示例进行对比,在并行世界中, ?不执行强制转换,并且没有自动包装try块结果:

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x = try {
        std::fs::File::open("foo").err_convert()?;
        Ok(())
    };

    x?;

    Ok(())
}

在这世上:

  • 很容易看出, try范围和fn本身都会导致成功而没有任何价值。 即使不尝试产生Result s,也很容易看到它。
  • 发生错误转换的地方很明显。
  • 错误转换可以移动到x?表达式中,使try作用域特定于std::fs::File操作。
  • 所有类型提示均从类型签名流畅地链接。 对于机器和人类。
  • 仅在我们实际上要将错误归并为另一个独立错误的情况下,才需要由用户进行类型提示。

在那个平行的宇宙中,我会很高兴。

@phaylon我很欣赏您以谨慎的方式撰写该评论,但恐怕它没有建设性。 错误转换是?一部分,并且不会改变,因此,ok换行与其余讨论基本上是正交的。

如果曾经考虑过try函数(具有返回和抛出类型),那么也许值得考虑将try块分配的语法是类似的。

例如

try fn foo() -> u32 throw String {
  let result = try: u32 throw String {
    123
  };
  result?
}

对不起,如果已经讨论过了,但是使用的好处是什么

try fn foo() -> u32 throw String { ... }

或类似的相对于

fn foo() -> Result<u32, String> { ... }


看起来好像语法重复。

据我了解, @ gorilskij的主要优点是获得Ok包装。 否则,您必须编写:

fn foo() -> Result<u32, String> {
    try {
        // function body
    }
}

有些人也更喜欢throws因为他们发现例外术语是相关的。

就个人而言,我希望尽可能远离出现异常情况。

这不是讨论try fn话题,所以请不要再切线了。 该线程用于try块的公认功能,而不是潜在的功能(到目前为止,不是RFCd) try fn

我只是注意到,最初针对? RFC是使用Into而不是From 。 它指出:

当前的RFC为此目的使用std::convert::Into特征(它具有从From的全面暗示转发)。

尽管将确切的转换方法留为未解决的问题Into是(大概)的基础上从引导优选From

在泛型函数上指定特征范围时,建议使用Into不是使用From 。 这样,直接实现Into也可以用作参数。

然而,在Try特质RFCInto不再提及,并且转换使用完成From来代替。 这也是代码现在使用的内容,即使对于?
https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

这似乎很不幸,因为没有从IntoFrom 。 这意味着实现Into (而不是From )的错误(由于遗留原因或出于其他一些原因)不能与? (或Try )。 这也意味着,遵循标准库中建议在边界中使用Into任何实现都不能使用? 。 通过示例,标准库建议我编写:

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where E: Into<MyError>

但是,如果这样做,则无法在函数主体中使用? 。 如果我想这样做,我必须写

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where MyError: From<E>

但是,如果我这样做,则错误类型实现Into而不是From用户不能使用此功能。 请注意,由于基于FromInto隐式隐式表示,因此相反的说法不成立。

立即修复? (?_very_很不幸–也许在下一版中?),可能为时已晚(?),但我们至少应确保不要在Try那条路上走得更深。

@jonhoo @cuviper尝试将减法从From 60796中的Into以检查#38751,正是由于From导致大量的推理中断-> Into总括式隐含使rustc很难处理身份转换的常见情况。 决定不值得花这么多钱打破推断。

@jonhoo您可能还会从niko提供的评论中找到此评论:

特征系统中也有一个硬编码的限制。 如果您必须解决?X: Into<ReturnType>类的目标,我们将无法解决,但如果您必须解决ReturnType: From<?X>类的目标,我们将有可能成功并推断?X

编辑:在这里, ?X引用一些未知的推理变量。 当今特征系统的硬编码限制是,必须至少部分推断Self类型,以便我们探索该选项。

TL; DR的缺点是,用Into推断比较困难,并且以特质求解程序运行方式的固有方式进行。

@KrishnaSannasi @ CAD97谢谢,这很有帮助! 我仍然担心我们会过度稳定基于From因为我们会永久放弃Into的实现者。 期望这里的推论最终会变得更好吗? 是否应更改在边界范围内偏爱Into的指南? 我们是否期望使用1.41中的新特征一致性规则(我认为是)不再有理由只实现Into并考虑所有这些impls错误?

如果推断足够好,则应该向前兼容,以后再更改为Into 。 考虑到From意味着Into ,我们可以打破的最糟糕的情况是推论

Try特性)是否允许?Into / From特性配合使用,实现了实现Try类型的通用范围Result本身)?

?在闭包或函数中返回,例如impl Into<Result>

(似乎晚上没有尝试该方法吗?)

我渴望看到这种情况稳定下来。 在阅读了该线程和#70941之后,我认为摘要应作如下更新:

  1. “决心是否catch块应该‘包装’的结果值”应该被选中,“解析为

  2. 对这些推理问题的新关注。 也许像这样:

    • []由于类型推断问题而导致的人体工程学难题。

ISTM可以通过以下方式解决这个最后的问题:

(其中一些选项不是互斥的。)

感谢您的关注,希望对您有所帮助。

类型归属有很多问题(语法上或其他方面),似乎不太可能很快实现,更不用说稳定了。 在类型归属语法上阻止try块似乎不合适。

回退到Result可能会有所帮助,但不能解决错误类型的类型推断问题: try { expr? }? (或者实际上是更复杂的等效项)实际上对.into()两次调用,这为编译器在中间类型上提供了太多的灵活性。

@ijackson感谢您主动总结当前状态。 我认为您是对的,我们可以通过多种方法来改进try块,但是问题之一是我们不确定要执行哪种方法,部分原因是每种解决方案都有其独特的缺点。

不过,关于类型归属,我确实觉得实现方面的挑战并不那么困难。 不管怎么说,这可能是一个不错的选择,可以吸引他们的注意力并尝试将其推向终点。 我不记得关于语法还是类似的东西有很多争议。

尼科·马塔基斯(Niko Matsakis)在2020年8月5日,星期三,00-07:02:29:06PM写道:

不过,关于类型归属,我确实觉得实现方面的挑战并不那么困难。 不管怎么说,这可能是一个不错的选择,可以吸引他们的注意力并尝试将其推向终点。 我不记得关于语法还是类似的东西有很多争议。

我记得,主要关注的是允许类型归属
到处都会有实质性的语法变化,并且可能
限制一个。 我不记得全部细节,只是担心是
提高。

我个人认为人体工程学问题还不错,以至于现在不值得稳定该功能。 即使没有表达式类型说明,引入let绑定也不是很丑的解决方法。

同样, try块在宏中可能很有用。 特别是,我想知道@withoutboats优秀的fehler库是否可以将宏程序的主体包装在try ,从而减少我们的宏系统中的不足问题。

我遇到了很多我想经常使用try块的地方。 最好把它弄清楚。 就个人而言,如果需要获得在线尝试块,我绝对会100%牺牲类型归属。 我还没有发现自己说过“我希望在这里输入类型”的情况,但最终还是做了一个IIFE来大量模拟try块。 留下长期有用的功能不稳定是因为它与另一个长期不稳定的功能冲突是非常不幸的情况。

更具体地说,我发现自己在返回Result的函数中时执行此操作,但是我想对返回Option的东西进行某种处理。 就是说,如果Try总体上是稳定的,我可能仍然更喜欢try块,因为我实际上并不希望从main函数返回,但是如果链中有任何内容,则给出某种默认值没有。 我倾向于在序列化样式代码中发生这种情况。

就个人而言,我希望输入归因比尝试块要频繁得多(尽管我有时会两者都想要)。 特别是,我经常在“类型调试”方面苦苦挣扎,在这种情况下,编译器会推断出与我期望的类型不同的类型。 通常,您必须在某个地方添加新的let绑定,这确实具有破坏性,并导致rustfmt破坏撤消历史记录。 此外,在很多地方,类型标识可以避免多余的涡轮鱼。

相反,我可以使用and_then或其他组合器尽早终止而不退出。 也许没有那么干净的尝试块,但也没有那么糟糕。

@steveklabnik @ mark-im尝试块和类型说明没有任何冲突,这不是一个功能或另一个功能的问题。 只是try块具有不符合人体工程学的类型推断错误,而广义类型归因可能是解决该问题的一种方法,但是由于广义类型归因不是近期功能,甚至也不是肯定的事情,

这甚至不意味着我们不会将广义类型归因作为问题的解决方案。 一个值得研究的选择是“按原样稳定尝试,希望有朝一日广义类型归因能够解决该问题。” 所说的只是防止类型归属的稳定。


由于GitHub和此处讨论的许多其他主题的局限性,我不得不承认@ rust-lang / lang有点难以理解从该线程进行类型推断失败的细微差别。 由于就如何处理推理故障达成决定是阻止try块稳定的唯一方法,因此,我认为如果我们召开会议来讨论这一点将是有益的,并且有人可以着眼于深入理解推理问题。

例如,我想到的一个问题是:这个推断问题是否专门因为我们在Try允许的转换灵活性? 我知道这个决定已经被讨论到死了,但是如果这样的话,这似乎是相关的新信息,可以证明改变Try性状的定义是正确的。

@withoutboats我同意需要在一个地方收集所有信息,并希望将此功能推到终点。 就是说,我认为上次我们在这里进行调查时也很清楚,由于向后兼容性,更改Try可能很困难- @cramertj提到了一些特定的Pin暗示,即IIRC。

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

相关问题

Robbepop picture Robbepop  ·  3评论

drewcrawford picture drewcrawford  ·  3评论

tikue picture tikue  ·  3评论

behnam picture behnam  ·  3评论

wthrowe picture wthrowe  ·  3评论