Rust: 将 `!` 提升为类型的跟踪问题 (RFC 1216)

创建于 2016-07-29  ·  259评论  ·  资料来源: rust-lang/rust

rust-lang/rfcs#1216 的跟踪问题,它将!提升为一个类型。

待解决的问题

有趣的事件和链接

A-typesystem B-RFC-approved B-unstable C-tracking-issue F-never_type Libs-Tracked T-lang T-libs finished-final-comment-period

最有用的评论

@petrochenkov忘记!并查看枚举。

如果我有一个包含两个变体的枚举,我可以用两种情况对其进行匹配:

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

这适用于任何 n,而不仅仅是两个。 所以如果我有一个零变体的枚举,我可以用零个案例匹配它。

enum Void {
}

let void: Void = ...;
match void {
}

到现在为止还挺好。 但是看看我们尝试匹配嵌套模式会发生什么。 这是Result内的两个变量枚举的匹配。

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

我们可以在外部扩展内部模式。 有两个Foo变体,因此Err有两种情况。 我们不需要单独的 match 语句来匹配ResultFoo 。 这适用于具有任意数量变体的枚举...... _except zero_。

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

为什么这行不通? 我不会称之为修复这个为无人居住的类型添加“特殊支持”,我称之为修复不一致。

所有259条评论

哈扎!

这里有一个 WIP 实现: https :

它的当前状态是:它使用 old-trans 构建并且可用,但有几个失败的测试。 某些测试由于错误导致if (return) {}类的代码在传输过程中崩溃而失败。 其他测试与链接时间优化有关,对我来说一直是错误的,所以我不知道它们是否与我的更改有关。

我目前的路线图是:

  • 让它与 MIR 一起工作。 这希望不会太难,因为这是我开始实施它的方式,但是我遇到了 MIR 在编译期间构建段错误的问题。
  • 从编译器中清除过时的分歧内容( FnOutputFnDiverging和相关)。
  • 将新类型隐藏在特征门后面。 这意味着,当该功能被禁用时:

    • !只能解析为返回位置的类型。

    • 发散类型变量默认为()

  • 弄清楚当默认的()用于解决特征时,我们如何发出兼容性警告。 一种方法是向 AST 添加一个名为DefaultedUnit的新类型。 这种类型的行为类似于() ,在某些情况下会变成() ,但在解析特征时会引发警告(如() )。 这种方法的问题是我认为很难捕捉并修复实现中的所有错误 - 我们最终会破坏人们的代码以防止他们的代码被破坏。

有什么需要添加到这个列表中的吗? 难道只有我在做这个吗? 这个分支应该移到主存储库上吗?

弄清楚当默认的()用于解决特征时,我们如何发出兼容性警告。 一种方法是向 AST 添加一个名为DefaultedUnit的新类型。 这种类型的行为类似于() ,在某些情况下会变成() ,但在解析特征时会引发警告(如() )。 这种方法的问题是我认为很难捕捉并修复实现中的所有错误 - 我们最终会破坏人们的代码以防止他们的代码被破坏。

@eddyb,@ arielb1,@anyone_else:这种方法的思考? 我几乎已经到了这个阶段(没有我(非常缓慢地)试图修复的几个失败的测试)。

我们应该为什么特性实现!? 最初的 PR #35162 包括 Ord 和其他一些。

!不应该自动实现 _all_ 特征吗?

这种代码相当常见:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

    fn do_something(&self) -> Self::Bar;
}

我希望!可用于Foo::Bar以表明Bar永远不会实际存在:

impl Foo for MyStruct {
    type Bar = !;
    fn do_something(&self) -> ! { panic!() }
}

但这只有在!实现所有特征时才有可能。

@tomaka有关于它的 RFC: https :

问题是,如果!实现了Trait它也应该实现!Trait ...

问题是,如果! 实现 Trait 它也应该实现 !Trait...

然后特殊情况!使其忽略任何特征要求?

@tomaka !不能自动实现 _all_ 特征,因为特征可以有静态方法和关联的类型/常量。 它可以自动实现只有非静态方法的特征(即采用Self )。

至于!Trait ,有人建议!可以自动实现Trait _and_ !Trait 。 我不确定这是否合理,但我怀疑负面特征根本不合理。

但是,是的,如果!可以在您为这些情况提供的示例中自动实现Baz可能会很好。

我们究竟什么时候将发散类型变量默认为() / ! ,什么时候我们会抛出关于无法推断足够类型信息的错误? 这是在任何地方指定的吗? 我希望能够编译以下代码:

let Ok(x) = Ok("hello");

但是我得到的第一个错误是“ unable to infer enough type information about _ ”。 在这种情况下,我认为_默认为!有意义的。 然而,当我围绕默认行为编写测试时,我发现将类型变量设为默认值非常困难。 这就是为什么这些测试如此复杂的原因。

我想清楚地了解为什么我们有这种默认行为以及应该何时调用它。

但是我得到的第一个错误是“无法推断出足够的关于 _ 的类型信息”。 在这种情况下,我认为 _ 默认为 ! 是有意义的。 然而,当我围绕默认行为编写测试时,我发现将类型变量设为默认值非常困难。 这就是为什么这些测试如此复杂的原因。

在我看来,这是一个非常好的主意。 例如, None将默认为Option<!>

@carllerche

unit_fallback测试当然是一种奇怪的演示方式。 一个不太宏的版本是

trait Balls: Default {}
impl Balls for () {}

struct Flah;

impl Flah {
    fn flah<T: Balls>(&self) -> T {
        Default::default()
    }
}

fn doit(cond: bool) {
    let _ = if cond {
        Flah.flah()
    } else {
        return
    };
}

fn main() {
    let _ = doit(true);
}

只有return / break / panic!()创建的类型变量默认为任何类型。

我们什么时候将发散类型变量默认为 ()/! 我们什么时候抛出关于无法推断足够类型信息的错误? 这是在任何地方指定的吗?

定义“指定”。 :) 答案是某些操作(不会在代码之外的任何地方写下来)要求在那时知道类型。 最常见的情况是字段访问 ( .f ) 和方法调度 ( .f() ),但另一个例子是 deref ( *x ),可能还有一两个。 需要这样做的原因大多是正当的——一般来说,有多种不同的方式来进行,如果不知道采取哪种方式,我们就无法取得进展。 (有可能重构代码,以便可以将这种需求注册为一种“未决义务”,但这样做很复杂。)

如果您一直到 fn 的末尾,那么我们将运行所有挂起的特征选择操作,直到达到稳定状态。 这是应用默认值(例如 i32 等)的点。 最后一部分在 RFC 中描述,讨论用户指定的默认类型参数(尽管 RFC 通常需要工作)。

@canndrew那些看起来有点类似于https://github.com/rust-lang/rust/issues/12609

男孩,这是一个老错误! 但是,是的,我会说我的 #36038 是个骗局(我以为我以前在某处见过它)。 我不认为!真的可以被认为是黄金时段,直到它得到解决。

!是否计划影响模式匹配的详尽性? 当前可能错误的行为示例:

#![feature(never_type)]

fn main() {
    let result: Result<_, !> = Ok(1);
    match result {
//        ^^^^^^ pattern `Err(_)` not covered
        Ok(i) => println!("{}", i),
    }
}

@tikue是的,这是上面列出的错误之一。

@lairy 哎呀,没有看到它,因为它没有列在顶部的复选框中。 谢谢!

是否有计划实施From<!> for TAdd<T> for ! (输出类型为! )? 我知道这是一个非常奇怪的具体要求——我试图在这个 PR 中同时使用两者。

From<!> for T肯定是。 Add<T> for !可能由 libs 团队决定,但我个人认为!应该实现它具有逻辑的、规范的实现的每个特征。

@canndrew谢谢! 我习惯了 Scala 的Nothing trait,它是每种类型的子类型,因此几乎可以在任何值出现的地方使用。 然而,我绝对赞同理解impl All for !或类似的对 rust 类型系统的影响的愿望,特别是关于负面特征边界等。

根据https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 From<!> for T存在一致性问题。

啊对,是的。 我们需要为此做些什么。

如果 trait impls 可以明确声明它们被其他 trait impls 覆盖,那就太好了。 就像是:

impl<T> From<T> for T
    overridden_by<T> From<!> for T
{ ... }

impl<T> From<!> for T { ... }

这不是由专业涵盖吗? 编辑:我相信这是格子 impl 规则。

尽可能避免对无人居住的类型提供特殊支持是一种可行的选择吗?

所有这些match (res: Res<A, !>) { Ok(a) /* No Err */ }Result特殊方法看起来很做作,就像为了特性而特性,并且似乎不值得付出努力和复杂性。
我知道!@canndrew的一个宠物功能,他想进一步开发它,但也许从一开始就是一个错误的方向,#12609 甚至不是一个错误?

@petrochenkov #12609 不是 Never 类型的特殊功能。 这只是检测一些明显无法访问的代码的错误修复。

@petrochenkov忘记!并查看枚举。

如果我有一个包含两个变体的枚举,我可以用两种情况对其进行匹配:

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

这适用于任何 n,而不仅仅是两个。 所以如果我有一个零变体的枚举,我可以用零个案例匹配它。

enum Void {
}

let void: Void = ...;
match void {
}

到现在为止还挺好。 但是看看我们尝试匹配嵌套模式会发生什么。 这是Result内的两个变量枚举的匹配。

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

我们可以在外部扩展内部模式。 有两个Foo变体,因此Err有两种情况。 我们不需要单独的 match 语句来匹配ResultFoo 。 这适用于具有任意数量变体的枚举...... _except zero_。

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

为什么这行不通? 我不会称之为修复这个为无人居住的类型添加“特殊支持”,我称之为修复不一致。

@petrochenkov也许我误解了你在说什么。 #12609 线程中讨论了两个问题:

(0) 应该允许这段代码编译吗?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
}

(1) 应该允许这段代码编译吗?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
    Err(_) => ...,
}

按照目前的实施,答案分别是“否”和“是”。 #12609 在问题本身中具体谈到了 (1),但我在回答时想到了 (0)。 至于答案_应该_是什么,我认为(0)绝对应该是“是”,但对于(1)我也不确定。

@坎德鲁
制作 (1) 可能是合理的,即无法访问的模式、lint 而不是硬错误,而不管无人居住的类型如何, RFC 1445包含更多关于为什么这可能有用的示例。

关于 (0) 我或多或少被你的解释说服了。 如果这个方案自然地依赖于编译器中的模式检查实现并且删除比添加更多的特殊代码,我会非常高兴。

顺便说一句,我已经做了一个 PR 来尝试修复 (0) 在这里: https :

如果这个方案自然地依赖于编译器中的模式检查实现并且删除比添加更多的特殊代码,我会非常高兴。

奇怪的是,它没有。 但这可能更多地是我将它黑进现有代码的方式的产物,而不是没有一种优雅的方式来做到这一点。

我认为对于宏来说,(1) 不是一个硬错误是很有用的。

我认为 (1) 应该默认编译,但会从与此处相同的 lint 发出警告:

fn a() -> u32 {
    return 4;
    5
}
warning: unreachable expression, #[warn(unreachable_code)] on by default

当我们这样做时,面对!使某些模式无可辩驳有意义吗?

let res: Result<u32, !> = ...;
let Ok(value) = res;

我同意使非穷举匹配错误,但无法访问,即冗余模式只是一个警告似乎是有道理的。

我已经有一些 PR 坐了一段时间收集腐烂。 我能做些什么来帮助审查这些内容吗? 可能需要对它们进行一些讨论。 我说的是 #36476、#36449 和 #36489。

我反对认为发散类型(或类型理论中的“底部”类型)与空的enum类型(或类型理论中的“零”类型)相同的想法。 它们是不同的生物,尽管两者都没有任何价值或实例。

在我看来,底部类型只能出现在任何表示返回类型的上下文中。 例如,

fn(A,B)->!
fn(A,fn(B,C)->!)->!

但你不应该说

let g:! = panic!("whatever");

或者

fn(x:!) -> !{
     x
}

甚至

type ID=fn(!)->!;

因为没有变量应该有! ,所以没有输入变量应该有!

空的enum在这种情况下是不同的,你可以说

enum Empty {}

impl Empty {
    fn new() -> Empty {
         panic!("empty");
    }
}

然后

 match Empty::new() {}

也就是说, !Empty之间存在根本区别:您不能将任何变量声明为!但可以为Empty .

@earthengine在两种不适宜居住的类型之间引入这种(在我看来完全是人为的)区别有什么好处?

已经提出了许多没有这种区别的原因 - 例如,能够编写Result<!, E> ,让它与不同的函数很好地交互,同时仍然能够在Result上使用单子操作mapmap_err

在函数式编程中,空类型 ( zero ) 经常用于对函数不返回或值不存在的事实进行编码。 当您说bottom类型时,我不清楚您指的是哪种类型理论; 通常bottom是一个类型的名称,它是所有类型的子类型——但从这个意义上说,Rust 中的!不是空枚举都不是bottom 。 但同样, zero不是bottom类型在类型理论中并不少见。

也就是说, !Empty之间存在根本区别:您不能将任何变量声明为!但可以为Empty .

这就是这个 RFC 正在解决的问题。 !不是真正的“类型”,如果你不能像类型一样使用它。

@RalfJung
这些概念来自线性逻辑https://en.wikipedia.org/wiki/Linear_logic

由于这篇文章前面的文字有一些错误,我删除了它们。 一旦我做对了,就会更新这个。

top是一个可以以任何可能的方式使用的值

这在子类型中意味着什么? 它可以变成任何类型? 因为那是bottom

@eddyb我犯了一些错误,请等待我的新更新。

这个 PR - 在一天内合并 - 与我的模式匹配 PR发生了严重冲突,第二个模式匹配 PR 自从第一个变得不可合并,在它经过两个月的审查后不得不重写。

朋友们

@canndrew啊,我很抱歉。 =(我今天也打算为你的 PR 做...

抱歉,我不想发牢骚,但这一直在拖延。 我放弃了同时维护多个 PR 的尝试,现在我一直在尝试从 9 月开始进行这一更改。 我认为问题的一部分是时区差异——你们很多人在我睡觉的时候都在线,所以很难谈论这些东西。

鉴于异常是类型系统中一个合乎逻辑且必要的漏洞,我想知道是否有人认真考虑过用 ! 更正式地处理它们。

使用https://is.gd/4EC1Dk作为示例和参考点,如果我们过去了,并且。

1) 处理任何可能发生恐慌但不返回错误或结果的函数,使其类型签名从-> Foo隐式更改为-> Result<Foo,!> 2) any结果types would have their Error types implicitly be converted to enum AnonMyErrWrapper { Die(!),Error}```
3)由于! 是零大小的不可居住的广告,类型之间的转换成本为零,并且可以添加隐式转换以使其向后兼容。

当然,好处是异常被有效地提升到类型系统中,并且可以对它们进行推理,对它们执行静态分析等。

我意识到,从社区的角度来看,即使不是从技术角度来看,这也很重要。 :)

此外,这可能与未来可能的效果系统重叠。

@tupshin这是一个突破性的变化,至少没有很多体操。 如果您想要清晰,我建议禁用展开并手动使用“结果”。 [顺便说一句,这个问题并不是真正提出这种事情的地方—— !的设计不是你要质疑的东西,所以这纯粹是未来的工作。]

我意识到它会是多么的破碎,至少没有重要的体操,并且公平地说它完全是未来的工作。 是的,我对计划的内容很满意! 到目前为止,就它而言。 :)

@nikomatsakis关于悬而未决的问题,这两个可以打勾

  • 来自 #35162 的代码清理,通过返回位置而不是调用 expr_ty 将 typeck 重新组织为线程类型
  • 解决比赛中无人居住类型的处理

我要开始另一个破解这个:

  • 如何为依赖 (): Trait fallback 的人实施警告,该行为将来可能会改变?

我计划向TyTuple添加一个标志,以表明它是通过默认一个发散类型变量创建的,然后在特征选择中检查该标志。

@坎德鲁

我计划向 TyTuple 添加一个标志,以表明它是通过默认发散类型变量创建的,然后在特征选择中检查该标志。

太好了!

好吧,也许很棒。 =) 有两个语义等价的类型( () vs () )听起来有点复杂,但我想不出更好的方法。 =( 无论如何,由于区域的原因,应该为它准备很多代码。

我的意思是我给TyTuple添加一个布尔值

TyTuple(&'tcx Slice<Ty<'tcx>>, bool),

这表明如果我们尝试选择它的某些特征,我们应该对这个(单元)元组发出警告。 这比我添加另一个TyDefaultedUnit原始方法更安全。

希望我们只需要在一个警告周期内保留那个布尔值。

关于uninitialized / transmute / MaybeUninitialized问题,我认为明智的做法是:

  • MaybeUninitialized添加到标准库中,在mem
  • uninitialized上添加一个检查,它的类型是已知有人居住的。 否则提出警告并附上说明这将是未来的严重错误。 建议改用MaybeUninitialized
  • transmute上添加一个检查,它的“to”类型只有在它的“from”类型也是无人居住的情况下。 同样提出一个警告,这将成为一个硬错误。

想法?

似乎对&!的语义存在一些分歧。 在#39151 之后,启用never_type功能门后,它在比赛中被视为无人居住。

如果!自动特征实现不存在,至少从std实现适当的特征会非常有帮助。 void::Void一个巨大限制是它位于std ,这意味着全面的impl<T> From<Void> for T是不可能的,并且限制了错误处理。

我认为至少From<!>应该为所有类型实现, ErrorDisplayDebug应该为!

我认为至少应该为所有类型实现From<!>

不幸的是,这与From<T> for T impl 冲突。

不幸的是,这与From<T> for T impl 冲突。

至少在执行格 impl 专业化之前。

好点。 我希望有一天能看到它完成。

应该[T; 0]子类型[!; 0]&[T]子类型&[!]吗? 对我来说,答案应该是“是”,这似乎很直观,但当前的实现并不这么认为。

[!; 0]是有人居住的, &[!] [!; 0]也是有人居住的,所以我会说不。 此外,这次我们没有使用子类型。

[!; 0]&[!]都不应无人居住,这两种类型都可以接受值[] (或&[] )。

没有人说他们是或应该是无人居住的。

let _: u32 = unsafe { mem::transmute(return) };
原则上这可能没问题,但编译器抱怨“在 0 位和 32 位之间转换”。

然而,允许转换! -> () 。 我没有任何特别的理由指出这一点。 这是一个不一致,但我想不出它的实际或理论问题。

我不期望子类型化,并且会以避免使编译器中的所有逻辑复杂化为由反对它。 基本上,我不想要与区域或区域绑定无关的子类型。

是否有可能以对所有输入和输出类型通用的方式为!实现FnFnMutFnOnce

就我而言,我有一个通用构建器,它可以接受一个函数,在构建时将调用该函数:

struct Builder<F: FnOnce(Input) -> Output> {
    func: Option<F>,
}

由于funcOption ,使用None的构造函数无法推断F 。 因此fn new实现应该使用 Bang:

impl Builder<!> {
    pub fn new() -> Builder<!> {
        Builder {
            func: None,
        }
    }
}

构建器的func函数应该看起来像这样,可以在Builder<!>Builder<F>上调用:

impl<F: FnOnce(Input) -> Output> Builder<F> {
    pub fn func<F2: FnOnce(Input) -> Output>(self, func: F) -> Builder<F2> {
        Builder {
            func: func,
        }
    }
}

现在的问题:目前Fn*特征没有为! 。 此外,你不能有一个通用的关联类型(例如Output中的Fn )。 那么是否有可能为!实现Fn* trait 系列,但它在所有输入和输出类型上都是通用的?

这听起来很矛盾。 <! as Fn>::Output不需要解析为单一类型吗?

<! as Fn>::Output! ,不是吗?

理论上<! as Fn>::Output应该是! ,但当前的类型检查器希望关联类型完全匹配: https :

@SimonSapin它确实需要解析为单一类型,这就是我提出问题的原因。 从语言的角度来看,这是完全正确的行为。 但是从库的角度来看,如果保持当前行为,在泛型函数的上下文中!的使用将非常有限。

应该是下面的代码

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    #[allow(unreachable_code)]
    *with_print(10,&return)
}

打印10 ? 现在它不打印任何东西。 或者任何其他会延迟return直到打印的解决方法?

我的意思是,正如我之前所说的,实际上有两种不同的!类型:一种是懒惰的,一种是渴望的。 通常的符号表示急切的,但有时我们可能需要懒惰的。


由于@RalfJung对使用&return的先前版本感兴趣,我再次调整了代码。 再说一次,我的观点是我们应该能够稍后延迟return 。 我们可以在这里使用闭包,但我只能使用return ,而不是breakcontinue等。

例如,如果我们可以写

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    for i in 1..10 {
        if i==5 { with_print(i, /* delay */break) }
        with_print(i, i);
    }
}

中断延迟到5被打印出来。

@earthengine不——那绝对不应该打印任何东西。 !是无人居住的,这意味着不能创建!类型的值。 因此,如果您引用了!类型的值,那么您就处于死代码块中。

return具有!类型的原因是因为它发散了。 使用return表达式结果的代码永远无法执行,因为return永远不会“完成”。

oO 我不知道你可以写&return 。 那没道理,为什么要让你取return的地址?^^

但是,无论如何, @earthengine在您的代码中,在调用quit_with_print之前评估参数。 评估第二个参数运行return ,它终止程序。 这就像编写foo({ return; 2 })类的东西——永远不会执行foo

@RalfJung return!类型的表达式,就像breakcontinue 。 它有一个类型,但它的类型是无人居住的。

!已经实施并在夜间工作了一年。 我想知道需要做些什么才能使其稳定。

尚未解决的一件事是如何处理可以返回!的内在函数(即uninitializedptr::readtransmute )。 对于uninitialized ,最后我听说有一个共识是它应该被弃用,以支持MaybeUninit类型和可能的未来&in&out&uninit参考。 没有针对此的 RFC,尽管在较早的 RFC中我提议弃用uninitialized仅适用于未实现新的Inhabited特征的类型。 也许该 RFC 应该被废弃并替换为“弃用uninitialized ”和“添加MaybeUninit ”RFC?

对于ptr::read ,我认为将其保留为 UB 很好。 当有人调用ptr::read他们断言他们正在读取的数据是有效的,而在!的情况下,它绝对不是。 也许有人对此有更细微的看法?

修复transmute很容易 - 只需将转换为无人居住的类型(而不是像目前那样的 ICEing)变成一个错误即可。 有一个 PR来解决这个问题,但它被关闭的原因是我们仍然需要更好地了解如何处理未初始化的数据。

那么我们现在在哪里呢? (/cc @nikomatsakis)? 我们是否准备好继续弃用uninitialized并添加MaybeUninit类型? 如果我们在用!调用时使这些内在函数恐慌,这是否是一个合适的权宜之计,可以让我们稳定!

还有列出的未决问题:

我们应该为!实现哪些特征? 最初的 PR #35162 包括Ord和其他一些。 这可能更像是一个 T-libs 问题,所以我将这个标签添加到问题中。

目前有一个相当基本的选择: PartialEqEqPartialOrdOrdDebugDisplayError 。 除了Clone绝对应该添加到该列表中之外,我看不到任何其他至关重要的内容。 我们需要阻止稳定吗? 如果我们认为合适,我们可以稍后添加更多实现。

如何为依赖(): Trait回退的人实施警告,该行为将来可能会改变?

resolve_trait_on_defaulted_unit警告已实施并且已经稳定。

强制中!期望语义 (#40800)
哪些类型变量应该回退到! (#40801)

这些似乎是真正的阻滞剂。 @nikomatsakis :你有什么具体的想法来处理这些正在等待实施的事情吗?

!也实现SyncSend是否有意义? 在某些情况下,错误类型具有Sync和/或Send边界,在这种情况下,将!用作“不能失败”会很有用。

@canndrew我不同意在不安全的代码中禁止它。 unreachable crate 使用 transmute 来创建无人居住的类型,因此提示优化器永远不会出现某些代码分支。 这对优化很有用。 我计划以有趣的方式使用这种技术制作我自己的板条箱。

@Kixunil在这里使用https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html不是更明确吗?

@RalfJung会,但这是不稳定的。 提醒我禁止转变为无人居住的类型将是一个突破性的变化。

@jsgf抱歉,是的, !还实现了所有标记特征( SendSyncCopySized )。

@Kixunil嗯,真可惜。 不过,稳定unreachable内在因素会好得多。

不是稳定阻止程序(因为性能,而不是语义),但似乎Result<T, !>没有在枚举布局或匹配代码生成中使用!的无人区: https :

这是一个如此模糊的想法,我几乎为把它丢在这里而感到内疚,但是:

粉笔可能有助于回答一些与!: Trait实现有关的更棘手的问题吗?

@ExpHP我不这么认为。 为没有关联类型/常量且只有非静态方法的特征自动实现!: Trait被认为是合理的。 这只是我们是否愿意这样做的问题。 其他特征的自动实现实际上没有意义,因为编译器需要为关联的类型/常量插入任意值并发明它自己的任意静态方法实现。

rustc 中已经有一个resolve-trait-on-defaulted-unit lint。 是否应该勾选相应的项目?

@canndrew将关联类型设置为!是否不合理?

@taralx这不是

trait Foo {
    type Bar;
    fn make_bar() -> Self::Bar;
}

impl Foo for ! {
    type Bar = (i32, bool);
    fn make_bar() -> Self::Bar { (42, true) }
}

@lairy这取决于您对什么是“有效”代码的看法。 对我来说,我很乐意接受您提供的代码“无效”,因为您不应该从!获得任何东西。

@earthengine我不明白你想表达的意思。 您可以在没有实例的类型上调用静态方法。 您是否可以从! “得到任何东西”与此无关。

没有理由不能从!类型中获取任何内容。 你不能任何!虽然,这就是为什么非静态方法!可以不强制对程序员任何决定的。

我认为最容易记住的规则是实现任何 trait,只要有 1 个有效的实现(不包括那些增加了比!已经引起的更多分歧的那些)。

我们采用的任何东西都应该与之相同或更保守(没有自动实现也适合)。

@Ericson2314我不明白这是什么意思,你能举一些例子吗?

@SimonSapin “不再有分歧”规则意味着没有panic!()loop { } ,但是已经在范围内的v: !很好。 排除了这些表达式后,许多特征只有一个可能的 impl for ! 那个类型检查。 (其他人可能有一份非正式合同,排除了除 1 项之外的所有内容,但我们无法自动处理这些。)

// two implementations: constant functions returning true and false.
// And infinitely more with side effects taken into account.
trait Foo { fn() -> bool }
// Exactly one implementation because the body is unreachable no matter what.
trait Bar { fn(Self) -> Self }

从数学上讲, !是一个“初始元素”,这意味着对于参数中包含!的类型签名,只有一个实现,即所有实现都相等——当从外面观察。 这是因为它们都不能被调用。

究竟是什么阻碍了它的稳定? 只是mem::uninitialized情况还是其他情况?

@arielb1 :我认为它也是 #40800 和 #40801。 你知道这些是什么状态吗?

关于这个已经写了哪些文档,或者人们打算为此写些什么? 随着最近对 std 文档(#43529、#43560)中原始类型页面的添加, ! 在那里输入获取页面? 如果还没有更新,参考文献可能也应该得到更新。

是否有任何计划允许指定 asm 正在发散,以便我们可以编写类似的内容?

#[naked]
unsafe fn error() -> !{
  asm!("hlt");
}

不必使用loop{}来安抚类型检查器?

编辑:虽然我认为在非常低级的代码中使用core::intrinsics::unreachable可能是可以接受的。

@Eroc33!是一种类型时,您可以这样做:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");
  std::mem::uninitialized()
}

在此之前,您可以这样做:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");

  enum Never {}
  let never: Never = std::mem::uninitialized();
  match never {}
}

@Kixunil !是一种夜间类型,您需要使用asm! 。 这通常是这样完成的:

#[naked]
unsafe fn error() -> ! {
    asm!("hlt");
    std::intrinsics::unreachable();
}

@Kixunil使用std::intrinics::unreachable()可以更轻松地完成,它确切地指定了之前的代码正在发散。

@Kixunil从上面的讨论std::mem::uninitialized能够返回!似乎可能会发生变化? 除非我误解了?

从上面的讨论std::mem::uninitialized能够返回!似乎可能会发生变化? 除非我误解了?

我认为uninitialized最终将被完全弃用,并且作为止损,当与!一起使用时会在运行时出现恐慌(尽管这不会影响这种情况)。

我还认为我们需要在 libcore 的某个地方稳定std::intrinsics::unreachable并将其命名为unchecked_unreachable或其他名称以将其与unreachable!宏区分开来。 我一直想为此编写一个 RFC。

@eddyb啊,是的,忘了asm!()是不稳定的,所以std::intrinsics::unreachable()也可以使用。

@tbu-当然,我更喜欢那个而不是创建无人居住的类型。 问题是,它不稳定,所以如果以某种方式不安全地将代码分支标记为不可达,它不仅会破坏现有代码,而且还会使其无法修复。 我认为这样的事情会严重损害 Rust 的声誉(尤其是考虑到主要开发人员自豪地声称它是稳定的)。 尽管我很喜欢 Rust,但这样的事情会让我重新考虑它是有用的语言。

@Eroc33我的理解是有些人建议这样做,但这仅仅是建议,而不是明确的决定。 我反对这样的建议,因为它会破坏向后兼容性,迫使 Rust 成为 2.0。 我认为支持它没有任何害处。 如果人们关心错误,lint 会更合适。

@canndrew为什么你认为uninitialized会被弃用? 我无法相信这样的事情。 我觉得它非常有用,所以除非有一个很好的替代品,否则我看不出有任何理由这样做。

最后,我想重申一下,我同意intrinsics::unreachable()比当前的黑客更好。 话虽如此,我反对在有足够的替代品之前禁止甚至弃用这些黑客行为。

未初始化的替换是 union { !, T }。 你可以通过
ptr::read::<*const !>()ing 和许多其他类似的方法。

2017 年 8 月 8 日下午 3:58,“Martin Habovštiak”通知@github.com
写道:

@eddyb https://github.com/eddyb啊,是的,忘记了 asm!() 是
不稳定,因此也可以使用 std::intrinsics::unreachable() 。

@tbu- https://github.com/tbu-当然,我更喜欢那个而不是
创造无人居住的类型。 问题是,它不稳定,所以如果它是
以某种方式不安全地将代码分支标记为无法访问是不可能的,它不会
不仅会破坏现有代码,还会使其无法修复。 我认为这样的事情
会严重损害 Rust 的声誉(特别是考虑到主要开发者
自豪地声称它是稳定的)。 尽管我很喜欢 Rust,但这样的事情会
让我重新考虑它是有用的语言。

@Eroc33 https://github.com/eroc33我的理解是有些人
建议这样做,但这只是建议,而不是明确的决定。 和我
反对这样的建议,因为它会破坏向后兼容性,
强制 Rust 成为 2.0。 我认为支持它没有任何害处。
如果人们关心错误,lint 会更合适。

@canndrew https://github.com/canndrew为什么你认为未初始化
会被弃用吗? 我无法相信这样的事情。 我觉得它非常有用,
所以除非有一个很好的替代品,否则我看不出有任何理由
这样做。

最后,我想重申一下,我同意内在函数::unreachable()
比当前的黑客更好。 话虽如此,我反对禁止甚至
弃用这些黑客,直到有足够的替代品。


您收到此消息是因为您订阅了此线程。
直接回复本邮件,在GitHub上查看
https://github.com/rust-lang/rust/issues/35121#issuecomment-320948013
或静音线程
https://github.com/notifications/unsubscribe-auth/AApc0iEK3vInreO03Bt6L3EAByBHQCv9ks5sWFt3gaJpZM4JYi9D
.

@nagisa谢谢! 看起来它可以解决技术层面的问题。

是否有可能划出它的一个子集,以便!可以用作返回类型中的类型参数? 现在如果你有一个稳定的功能

fn foo() -> ! { ··· }

并且想要使用? ,你不能做通常的转换

fn foo() -> io::Result<!> { ··· }

棘手的强制转换和类型参数默认问题会影响这种情况吗?

40801 可以打勾。

我们应该在稳定!之前尝试修复 #43061 。

我没有看到任何提及 never_type 的未解决的 I-unsound 问题,因此我为此提交了一个新问题:#47563。 事情似乎假设[!; 0]是无人居住的,但我可以用一个简单的[]创建一个。

@dtolnay添加到问题标题

@canndrew你还好吗? 我希望看到!使用情况的一个子集得到稳定(不包括关于详尽无遗的规则)。 不过,我有点迷失了我们所处的位置,我认为它需要一个冠军。 你有时间做那个人吗?

@nikomatsakis我相信我可以找到一些时间来处理这些事情。 究竟需要做什么呢? 仅仅是与此问题相关的错误吗?

看起来@varkor已经开放 PR 来解决剩余的问题(太棒了!)。 据我所知,剩下要做的唯一事情就是决定我们是否对当前实现的特征感到满意,并移动/重命名特征门,使其仅涵盖详尽的模式匹配更改。

虽然另一件事是:我们是否想让mem::uninitialized::<!>()在运行时恐慌(它目前会导致 UB)? 还是我们应该暂时放弃这些变化? 我不了解不安全代码指南的内容。

我认为计划仍然是https://github.com/rust-lang/rfcs/pull/1892 ,我刚刚注意到你写的。 :)

@RalfJung有什么特别阻止的吗? 我今天可以写一个 PR,添加MaybeUninit并弃用uninitialized

@canndrew由于许多项目都希望支持所有三个发布渠道,并且uninitialized可用于稳定版,因此最好仅在稳定版渠道上提供替代品后才开始在 Nightly 上发出弃用警告。 通过 doc-comments 软弃用是可以的。

我创建了一个 PR 来稳定!https : @varkor的 PR 是否解决了剩余的

我还认为我们应该重新访问https://github.com/rust-lang/rfcs/pull/1699,这样人们就可以开始为!编写优雅的 trait 实现。

@canndrew :虽然该 RFC 未被接受,但它看起来与https://github.com/rust-lang/rust/issues/20021 中的提案非常相似

https://github.com/rust-lang/rust/issues/36479 也应该添加到问题标题中。

@varkor非常相似。 对于无法访问的代码工作,您是否注意到像这样的代码有任何问题现在被标记为无法访问?:

impl fmt::Debug for ! {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        *self    // unreachable!
    }
}

因为这几乎需要接受某种类似的提案。

@canndrew我最近一直提倡的东西——尽管我们还没有开发一个正式的模板——是一种“摘要问题”,试图清楚地描述我们正在稳定的内容。 你可以把它作为RFC的报告,试图在一个排序项目符号列表形式是什么是稳定的突出特点的总结,也什么不是

其中一部分将是指向演示相关行为的测试用例的指针。

你认为你可以尝试绘制这样的东西吗? 如果你喜欢某种“大纲”,我们可以一起聊聊——或者我可以试着勾勒一下。

相关问题:我们应该在稳定之前尝试解决https://github.com/rust-lang/rust/issues/46325吗? 也许没关系。

@nikomatsakis我投票赞成不要等待任何警告问题得到解决。 那是无害的。 如果没有进一步的真正担忧出现,我认为我们应该继续并稳定它。

@canndrew :我认为我没有真正研究过任何这样的情况,尽管我绝对认为当!稳定时,非常需要能够省略不可能的实现。

@nikomatsakis

你认为你可以尝试绘制这样的东西吗? 如果你喜欢某种“大纲”,我们可以一起聊聊——或者我可以试着勾勒一下。

我至少可以写一个草稿,你可以告诉我这是不是你的想法。 我会努力在接下来的几天内完成它。

@nikomatsakis 这样的东西?

摘要问题 - !

什么正在稳定

  • !现在是一个成熟的类型,现在可以用于任何类型位置(例如RFC 1216 )。 !类型可以强制转换为任何其他类型,示例参见https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs
  • 类型推断现在会将不受约束的类型变量默认为!而不是()resolve_trait_on_defaulted_unit lint 已停用。 出现这种情况的一个例子是,如果您有以下内容:

    // We didn't specify the type of `x`. Under some circumstances, type inference
    // will pick a type for us rather than erroring
    let x = Deserialize::deserialize(data);
    

    在旧规则下,这将反序列化() ,而在新规则下,它将反序列化!

  • never_type特征门是稳定的,尽管它曾经用于门的一些行为现在位于新的exhaustive_patterns特征门之后(见下文)。

什么是被稳定

我们应该在稳定之前尝试解决 #46325 吗?

像这样清理每一个松散的末端虽然很好,但它看起来并不是真正的障碍物。

@坎德鲁

像这样的东西?

是的,谢谢! 那太棒了。

缺少的主要内容是指向显示!行为方式的测试用例的指针。 受众应该是 lang 团队或其他密切关注的人,我认为,它并不是真正针对“通用”人员。 所以例如,我想要一些强制强制合法的地方的例子,或者可以使用! 。 我还希望看到睾丸向我们展示详尽的模式匹配(未启用特征门)仍然有效。 这些应该是指向存储库的指针。

@坎德鲁

像这样清理每一个松散的末端虽然很好,但它看起来并不是真正的障碍物。

嗯,这意味着我们将启用将立即改变行为的新代码(例如,我认为let x: ! = ...对某些表达式的行为会有所不同)。 如果可以,似乎最好解决。 也许你可以在你打开的 PR 上让它成为一个严重的错误,我们可以把它和 PR 上运行的现有弹坑混为一谈?

@nikomatsakis我已经用一些链接和示例再次更新了该摘要问题。 另外,对不起,我花了一段时间才弄明白,过去一周我一直很忙。

也许你可以在你打开的 PR 上让它成为一个严重的错误,我们可以把它和 PR 上运行的现有弹坑混为一谈?

完毕。

@rfcbot fcp 合并

我建议我们稳定!类型——或者至少是它的一部分,如此处所述公关在这里

我想要的一点数据是火山口运行。 我正在重新定位https://github.com/rust-lang/rust/pull/47630 (因为@canndrew刚刚没有响应 ping),所以我们可以获得这些数据。

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

  • [x] @Kimundi
  • [x] @alexcrichton
  • []@aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • []@pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

当前未列出任何问题。

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

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

哦,我刚刚想起了几件事:

  • 我们应该只考虑在新时代稳定这一点的想法。 特别是,对后备规则的更改是向后不兼容的——火山口运行将为我们提供某种影响的下限,但这只是一个下限。 但也许我们可以保留旧的回退规则,除非您处于新时代。
  • 其次,我相信这里的计划的一部分也是为何时适合为!实现特征制定一些指导方针。 TL; DR 是,如果 trait 中的方法在没有首先提供!值的情况下不可用,那么可以 - 所以为!实现Clone !是可以的,我认为,但实现Default不是。 换句话说,如果实现 impl 将要求您不panic!选择地

@nikomatsakis我们能否在新纪元中更改回退规则,但仍将!作为 2015 纪元中可用的类型?

@nikomatsakis

我们应该只考虑在新时代稳定这一点的想法。

上次我们进行火山口运行时(很久以前),更改后备规则的影响相当小。 一段时间以来,我们还一直在检查可能受更改影响的代码。

其次,我相信这里的计划的一部分也是为何时适合为!实现特征制定一些指导方针。

这在!的文档中提到

@西蒙萨平

我们能否在新纪元中更改回退规则,但仍然使 ! 作为 2015 年可用的类型?

是的

@坎德鲁

上次我们进行火山口运行时(很久以前),更改后备规则的影响相当小。 一段时间以来,我们还一直在检查可能受更改影响的代码。

是的。 让我们看看火山口怎么说。 但是,正如我所说,陨石坑只会给我们一个下限——这是一种“选择性改变”。 尽管如此,我怀疑你是对的,我们可以“逃脱”改变这一点,而不会对野外代码产生太大影响。

TL; DR 是,如果 trait 中的方法在没有首先提供!值的情况下不可用,那也没关系

这只是正常规则 - 当您可以以理智的方式实现它时添加一个 impl,其中“以理智的方式实现它”排除恐慌,但在存在无效数据的情况下包括“ex falso”UB。

@arielb1是的,但出于某种原因,人们往往会对!存在的此类事情感到困惑,因此似乎值得明确指出。

也许有一个安全的方法fn absurd(x: !) -> !会有所帮助,它被记录为一种安全的方式来表达无法访问的代码,或者在 libstd 的某个地方? 我认为有一个 RFC ……或者至少是一个 RFC 问题: https :

@RalfJung不是absurd函数只是identity吗? 为什么有用?

它与unsafe fn unreachable() -> !内在函数不同,后者不接受任何参数并且是非常未定义的行为。

嗯,是的,它主要是。 我在“不终止”的意义上使用了!返回类型。 (通常的absurdfn absurd<T>(x: !) -> T ,但这在 Rust 中似乎也没有用。)

我在想这可能有助于“人们在!的存在下往往对这样的事情感到困惑”——在某处有一些文档表明“ex falso”推理是一回事。

似乎我也遇到了错误的问题......我以为在某个地方有关于这种“ex falso”功能的讨论。 好像我错了。

fn absurd<T>(x: !) -> T也可以写成match x {} ,但如果你以前没有见过它很难想出。 至少值得在 rustdoc 和书中指出。

真是荒谬(x: !) -> T 也可以写成 match x {},但是如果你以前没见过的话很难想出它。

是的,这就是为什么我在 libstd 的某个地方建议了一个方法。 不知道最好的地方在哪里。

absurd好处在于您可以将其传递给高阶函数。 鉴于如何,我不确定这是否必要! 表现得如此。 统一,不过。

fn absurd<T>(x: !) -> T也可以写成match x {}

它也可以写成x (AFAIK) - 如果你有一个空的enum你只需要match enum

absurd(x: !) -> T函数对于传递给高阶函数很有用。 我在链接期货时需要这个(例如),其中一个具有错误类型!和另一个错误类型T 。 尽管!类型的表达式可以强制转换为T但这并不意味着!T会统一,因此有时您仍然需要手动转换类型。

同样,我认为Result类的类型应该有一个.infallible()方法,它将Result<T, !>Result<T, E>

类似结果的类型应该有一个 .infallible() 方法来转换 Result造成.

我本来希望这样的方法有类型Result<T, !> -> T

我本来希望这样的方法具有 Result 类型-> T。

这不是和unwrap吗? 由于Err情况无法访问,恐慌将被优化掉。

@Amanieu重点是一开始就没有恐慌。 我很想有一个 panic lint,如果我们依赖优化,那会被绊倒。 即使没有 lint,在重构过程中使用unwrap也会增加一个footgun,减少恐慌再次成为实时代码。

unwrap读起来像assert ——“运行时提前检查”(IIRC 甚至有人提议重命名它,但他们来得太晚了......遗憾的是)。 如果您不想要并且需要运行时检查, infallible会明确表示。

:bell: 根据上面的评论

@rfcbot fcp 取消

@aturon@pnkfelix没有勾选他们的复选框。

@cramertj提议被取消。

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

  • [x] @alexcrichton
  • [x] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • []@pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

当前未列出任何问题。

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

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

:bell: 根据上面的评论

我们在今天的会议上讨论了这个问题。 我仍然为是现在改变回退还

预计变化的影响很小。 所以实际上这两种方式都没有太大区别。 你可以在这里看到完整的分析,但总结是:

剩下两个实际的回归:oplog-0.2.0(实际上是它的依赖 bson-0.3.2 被破坏了)和 rspec-1.0.0-beta.4。 这两者都依赖于旧的回退行为,但幸运的是它们在编译时而不是运行时中断。 我将提交 PR 来修复这些板条箱。

但这在技术上仍然是一个突破性的变化(并且是自愿的)。 没有什么特别的理由我们必须改变类型变量的回退,除了()是一个非常糟糕的选择,而且代码依赖它的情况非常罕见。 很长一段时间以来,我们也一直在警告这一点。

我认为这更像是一个哲学问题:过去,我们出于需要进行了像这样的微小改变。 但是现在我们有了划时代的机制,它为我们提供了一种更有原则的方法来进行这种转换,而不会在技术上破坏任何东西。 它可能值得使用,只是出于原则。 另一方面,这意味着有时需要等待数年才能做出这样的改变。 当然,我们必须永久维护这两个版本,使语言规范等变得更加复杂。

我有点沮丧,但我认为总的来说,前后摇摆不定,我目前倾向于按计划“让我们普遍改变它”。

我知道我有偏见,但与dyn Traitcatch类的东西相比,我认为改变回退行为更像是一个错误修正。

此外,如果有人想跳上这个并说,“啊哈!Rust 毕竟破坏了向后兼容性!当它达到 1.0 时他们对稳定性的承诺是谎言!” 那么关于这个问题的非常深思熟虑的讨论表明,即使考虑到实际影响可以忽略不计,该决定也不是轻率的。

好吧,我说让我们改变它。

那么,作为一种妥协,我想为错误提供一个有用的注释,告知人们行为已经改变——这似乎是合理的。 这个想法是这样的:

  • 如果我们看到某个 trait 的错误,如!: Foo ,并且该 trait 是为(): Foo并且我们已经完成了回退(我们可以告诉错误报告代码这个事实),那么我们添加一个额外的节点。

最后的评论期现已完成。

顶部帖子中的那些未决复选框是否尚未选中? 该列表中是否有任何未完成的操作?

@earthengine我不认为我看到的两个特别相关——至于最重要的项目,关于 impls 集,我想现在暂时决定了。

基本上是一个非常小的集合。

@nikomatsakis我刚刚在这里创建了摘要问题: https :

是否考虑过将!设为匹配!类型值的模式? (我已经通过这个问题和最初的 RFC 问题做了一个快速的 grep,但没有找到任何相关的东西)。

我会发现这对于诸如StreamFuture类的类型很有用,这些map_err使用!模式,如果将来某个时间点的错误类型发生变化,则map_err调用将停止编译,而不是可能做一些不同的事情。

例如,给定

let foo: Result<String, (!, String)> = Ok("hello".to_owned());

这将允许编写更明确的

let bar: Result<String, String> = foo.map_err(|(!, _)| unreachable!());

而不是潜在的容易出错的

let bar: Result<String, String> = foo.map_err(|_| unreachable!());

或者稍微不那么容易出错的

let bar: Result<String, String> = foo.map_err(|(e, _)| e);

编辑:重新阅读https://github.com/rust-lang/rfcs/pull/1872的评论确实提到了引入!模式。 这纯粹是在match表达式的上下文中,我不确定它是否可以推广到闭包/函数参数。

@尼莫157

有没有考虑制作! 与 ! 的值匹配的模式类型?

有趣的。 我认为计划只是让你放弃这样的武器,在这种情况下——如果类型在未来改变——你仍然会得到一个错误,因为你的匹配现在不再是详尽的。 !模式的问题在于,如果匹配,则表示手臂是不可能的,这有点尴尬; 我想,我们希望通过不要求您提供要执行的表达式来解释这一点。 不过,有办法明确说明这种情况不会发生可能会很好。

实际上,扩展不可达模式处理可能是解决此问题的另一种方法。 如果可以证明匹配臂是不可能的,那么可以忽略臂内的代码。 那么当你有

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(bar?)
}

这仍然会像今天一样扩展(_我不是 100% 确定这是正确的扩展,但似乎足够接近了_)

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(match Try::into_result(bar) {
        Result::Ok(e) => e,
        Result::Err(e) => return Try::from_err(From::from(e)),
    })
}

但是Result::Err分支不会被类型检查,你不会得到今天得到的the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied错误。

我认为这与 RFC 1872 的提议不兼容,因为有一个显式的 arm 正在执行浅匹配,它不一定依赖于!值的有效性,并且可能只要该分支根本不使用e.0可以“安全地”运行关联的分支。

!是一个正类型,就像bool ,所以在不扩展闭包语法以允许多个分支的情况下,在闭包参数列表中对其进行模式匹配是不可能的。 例如。 也许我们可以允许闭包bool -> u32写成类似(丑陋的假设语法) [~ |false| 23, |true| 45 ~] -> u32 ,然后从任何空类型到u32的闭包可以简单地写成[~ ~] -> u32

写你原来的例子,你可以写foo.map_err(|_: (!, _)| unreachable!())虽然我更喜欢foo.map_err(|(e, _)| e)因为它避免使用unreachable!

!是正型

你说的积极类型是什么意思?

在不扩展闭包语法以允许多个分支的情况下,在闭包参数列表中对其进行模式匹配是不可能的

参数列表已经支持无可辩驳的子模式,例如

let foo: Result<String, ((), String)> = Ok("hello".to_owned());
let bar: Result<String, String> = foo.map_err(|((), s)| s);

我只是将!模式视为匹配!类型的所有 0 个值的无可辩驳的模式,与()是匹配所有 1 个值的无可辩驳的模式相同()类型。

我只会考虑! 模式作为匹配所有 0 值的无可辩驳的模式! 类型,与 () 相同是一个无可辩驳的模式,匹配 () 类型的所有 1 值。

好吧,但是 0 != 1. ;) 在!上匹配之后编写任何代码都是毫无意义的——例如,如果参数类型是(!, i32) ,那么|(!, x)| code_goes_here很愚蠢,因为该代码无论如何都已死。 我可能会写|(x, _)| match x {}来明确表示x!一个元素。 或者,使用我上面提出|(x, _)| absurd(x) 。 或者也许|(x, _)| x.absurd()

人们可以想像一个不同的语法,它允许一个写任何代码,像什么@canndrew上面写的。 match有任意数量的分支,所以特别可能没有分支——但是闭包只有一种情况,所以唯一有意义的模式是那些恰好有 1 种匹配方式的模式。 不是 0 方式。

太糟糕了,我们现在不能添加impl<T> From<!> for T 。 (它与From<T> for T重叠,除非有专门的东西?)我们以后可能无法添加它。 (在!稳定后的发布周期中,某些 crate 可能会实现From<!> for SomeConcreteType 。)

@SimonSapin好点! 这对于很多事情来说都是非常有用的构建块,我不想永远失去它。 我会认真考虑一次性破解以允许这两种情况。 在重叠的情况下,它们在语义上是一致的,因此不存在“操作不连贯”。

我会认真考虑一次性破解

然后该黑客需要在接下来的几天内登陆。 (或者在测试期间被反向移植。)

@SimonSapin添加这样的 hack 能有多快/多容易? 永远无法拥有From<!> for T真的很糟糕。

或者,我们可以快速发出警告,不要实施From<!> for SomeConcreteType

我不知道,取决于黑客是什么。 我认为 trait 特化可以启用两个重叠的 impls,如果有第三个用于交集,但这是否需要 trait 公开“可特化”?

不幸的是,专业化:

  • 需要将From::from更改为default fn ,这将在 std 之外“可见”。 只要专业化不稳定,我不知道我们是否想在标准特征中这样做。
  • 正如 RFC 1210 所接受的那样,特别不支持我正在考虑的语言功能(通过为交集编写第三个 impl 来消除两个重叠的 impls 的歧义,其中没有一个比另一个更具体)。

在这里,两个 impls 做完全相同的事情。 如果我们只是 hack 关闭一致性检查,将很难确定使用了哪个 impl,这通常是非常不健全的,但在这种情况下很好。 可能会在编译时引发一些恐慌,期待一个结果,但我们可以解决这个问题。

换句话说,我认为这比加快任何专业化要容易得多,谢天谢地。

是否有一个现有的 RFC 来形式化这个“如果所有涉及的 impls 都无法访问,则允许任意重叠”的想法? 似乎是一种值得考虑的有趣方法。

从来没听说过。 挂起来说,impl 本身并不是不可访问的,而是内部的方法都是不可调用的(包括没有关联的类型或常量,因为它们总是“可调用的”)。 我想我可以很快写一个,如果它被认为是在这样的事情上阻止了黑客。

只是提到可能导致的问题 #49593! 再次变得不稳定以获得更好的可查找性。

想知道!稳定后除了T: From<!>还有什么不能添加的impls。 我们可能应该在稳定之前尝试处理所有这些合理的实现,这与通常的!的实现不同,没有这样的匆忙。

来自此评论的交叉发布:

我想知道 - 为什么回退到 () 将“必须”改变的经典例子是什么? 也就是说,如果我们继续回退到 (),但仍然添加 !,它肯定会避免这种问题——并回退到 ! 不是最佳的,因为在某些情况下,类型变量会发生回退,最终会影响“实时”代码(目的当然不是这样,但正如我们发现的那样,很难防止泄漏)。

由于这种变化,出现了几次回归,我不得不说我对它并不完全满意:

(围绕这一点提名 lang-team 讨论。)

在会议期间,我们在这里退缩了一些选项——我感觉不太愿意做出改变,主要是因为它在某些极端情况下改变了现有代码的语义,并且不清楚新规则是否更好。 但主要是我认为我们决定再次尝试收集每个设计的优缺点会很棒,这样我们就可以进行更完整的讨论。 我想查看用例列表和陷阱。 @cramertj提到他们希望在不同情况Ok(33)的类型回退到Result<i32, !> ,而不是像今天那样出错; 我认为他们当时是说将return的后备保留为()将与这种更改不一致(尽管它们是正交的),并且可能会在未来产生冲突。 这是公平的。

Err反馈(#40801)的挑战在于那里也存在边缘情况,例如:

let mut x = Ok(22);
x = Err(Default::default());

其中x类型最终被推断为Result<T, !>

我想起了一个单独的计划,我必须尝试看看我们是否可以确定(也许是警告?或错误?) !回退是否曾经“影响”了实时代码——这证明相当困难,尽管看起来就像我们可以在实践中整理许多案例一样(例如,那个案例,可能

还讨论了我们是否可以在新版本中完全删除这种回退。 我怀疑它会破坏很多宏,但也许值得一试?

仅供参考,此功能导致 1.26 中的回归:#49932

(根据 https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041 设置标签。)

关于强制转换为!的清单项是否应该指向https://github.com/rust-lang/rust/issues/50350而不是引用此问题?

如果我想让这一切尽快稳定下来,我的能量在哪里最好?

@remexre我认为导致恢复稳定的问题的最佳描述是在https://github.com/rust-lang/rust/issues/49593

那么一个可行的解决方案是将特殊情况 Box 作为
盒子? 是否有任何对象安全特性无法以这种方式处理?

2018 年 7 月 8 日星期日上午 8 点 12 分,Ralf Jung [email protected]写道:

@remexre https://github.com/remexre我认为最好的描述
导致恢复稳定的问题在 #49593
https://github.com/rust-lang/rust/issues/49593


你收到这个是因为你被提到了。

直接回复本邮件,在GitHub上查看
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892
或静音线程
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

谢谢,
弥敦道

该讨论确实应该进入https://github.com/rust-lang/rust/issues/49593 ,但其中一个关键部分是Box::<T>::new需要T: Sized ,但是new根据返回类型推断T = Error (特征 DST)。

除了特殊情况的不雅之外,特殊情况Box::new是否有任何问题:

Box::new : T -> Box<U>
where T <: U,
      T: Sized

或者

Box::new : T -> Box<U>
where Box<T> <: Box<U>,
      T: Sized

首先,我们应该考虑!的大小应该是多少。 数学家会提出类似Inf ,但实际上它会是usize::MAX ,因此我们确保为这种类型分配空间的任何尝试都会失败,或者至少会失败panic

如果!Sized那么,没有什么可以阻止我们编译Box::new(x as !) ,但这基本上是panic!另一种方式,因为实际上没有内存模型可以分配usize::MAX字节。

对我来说!应该具有无限/ usize::MAX大小而不是 ZST 似乎并不明显?

use std::mem::size_of;
enum Void {}
fn main() { println!("{}", size_of::<Void>()); }

当前为0。

原因在呈现的文本中进行了解释。

bool : 两个有效值 => log(2)/log(2) = 1 位
() : 1 个有效值 => log(1)/log(2) = 0 位
! : 0 个有效值 => log(0)/log(2) = Inf bits

作为不精通相关理论的人,我认为坚持log(x)/log(y)公式是追求理论模型的优雅而损害实际使用。 (也就是为了自己的利益太聪明了)

直觉上,似乎!也应该是零大小,因为:

  • bool :除了类型系统之外,还需要空间来区分两个有效值 => log(2)/log(2) = 1 位
  • () : 需要空间来区分一个有效值 => 不需要超出类型系统的空间
  • ! : 需要空间来区分无效值=> 不需要超出类型系统的空间

嗯,实际上是 -infinity,这是有道理的,因为将 ! 作为一个字段到一个结构本质上把结构变成了! 以及(c + -inf = -inf)。 因此,由于我们正在处理 usize,因此 0 是我们必须表示的最接近的值。 (我认为 rustc 中的实际实现甚至使用了 -inf)。

直觉上,似乎!也应该是零大小

!根本不需要大小,因为永远不会使用该类型创建任何值。 这是一个无关紧要的问题。

0 个有效值 => log(0)/log(2) = Inf 位

log(0) 未定义; 它不是无限的。

另一方面,具有usize::MAX大小是强制执行无人居住的一种tenichcal 方式。 同意?

@CryZe @varkor

这也符合我试图推断其有效性的概念,我的直觉希望将!视为“完全不同级别的 ZST”。 (即!对类型系统来说就像()对内存分配一样。)

@CryZe
公式-Inf + c == -Inf是有道理的,但是当更换-Inf0usize它不持有任何更多。

另一方面,如果算术是“caped”的:任何溢出计算到usize::MAX那么usize::MAX完全符合公式。

大脑模型:如果你有一个!类型的对象,要分配一个包含它的结构体,你需要一个比!更大的结构体,但是你可以想象的最大结构体是usize::MAX 。 所以你需要的空间仍然是usize::MAX

为什么不只是将包含空类型( !或用户定义的enum Void {} )的任何类型的大小“钳制”到size=0,alignment=0 ? 这对我来说在语义上似乎更明智......

@remexre
因为这行不通。 示例: sizeof(Result<usize,!>) == sizeof(usize)

好吧, Result<usize, !>不直接包含!sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

不, sizeof(Result::Err(!)) == usize::MAX在我的提案中,因为Result::Err(!) :: Result<!,!>

规则应该是:在enum!大小被认为小于所有其他大小值,但在structs ,它是图像的最大大小。

结论:

  • !不是夏令时。 DST 不是Sized不是因为它们没有大小,而是因为隐藏了大小信息。 但是对于!我们知道它的一切。
  • !应该调整大小,因此Box::new(x as !)至少应该允许编译。
  • struct s 包含!大小应该与!enum s 包含!直接在一个变体中应该大小为如果该变体不存在。

无论我们考虑sizeof(!)==0还是sizeof(!)==usize::MAX ,我们都必须引入一些特殊的算术来允许上述情况:

sizeof(!)==0 :在结构中, 0 + n = 0 :担心:,在枚举中, max(0,n) = n脸红:。
sizeof(!)==usize::MAX :在结构中, usize::MAX + n =usize::MAX :neutral_face:,在枚举中, max(usize::MAX,n) = nflushed:。

不过有一个好处是usize::MAX :它在技术上阻止了任何尝试,甚至unsafe来构造这样的对象,因为这在任何实际系统中都是不可能的。

但是有一个好处是有利于 usize::MAX:它在技术上阻止了任何尝试,甚至是不安全的尝试来构造这样一个对象,因为这在任何实际系统中都是不可能的。

我的意思是,如果允许unsafe恶作剧: *transmute::<Box<usize>, Box<!>>(Box::new(0))给我一个!不管它的大小。 usize::MAX确实使编译器更有可能在有人尝试执行std::mem::zeroed<!>()时出错,我想,但我会将责任推给编写该不安全代码的人,而不是数学上面的怪异。

请注意, !大小 _actually_ 0 -- 不是MAX也不是-INF -saturated-to-0 -- 需要处理部分初始化,就像以前一样在https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 中找到(并在 https://github.com/rust-lang/rust/pull/50622 中实现)。

如果你想改变它的工作方式,请为此创建一个新的 issue 或 PR; 这不是地方。

@remexre

Box::new : T -> Box<U>
where T <: U,
      T: Sized

Box是(主要)一个库类型,它的new方法在src/liballoc/boxed.rs中用 Rust 语法定义。 您的描述假定 Rust 中不存在<:运算符。 这是因为 Rust 中的子类型仅通过生命周期参数存在。 如果您想阅读或观看更多内容:

!强制转换为任何类型,但这是不是任何类型的弱。 (例如,对于任何'aVec<&'static Foo>也是Vec<&'a Foo> ,但是没有从Vec<!>Vec<Foo>的隐式转换:http:// /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

我认为这个想法是你可以在unsafe模式下做任何你认为合适的事情,但是如果你不明智地去做,你就必须面对 UB。 如果!的官方大小是usize::MAX ,这应该足够提醒用户这种类型不应该被实例化。

另外,我正在考虑对齐。 如果 uninhabited 类型的大小usize::MAX ,则很自然地将其对齐方式也设置为usize::MAX 。 这有效地将有效指针限制为空指针。 但是根据定义,空指针是无效的,所以我们甚至没有这个类型的有效点,这对于这种情况来说是完美的。

@ScottAbbey
我专注于这个问题,以确保Box::new(x as !)没有问题,这样我们就可以继续稳定这一点。 建议的方法是让!有一个大小。 如果 ZST 是标准,那么应该没问题。 我不再争论了。 只需实现sizeof(!)==0

设置它的对齐方式也是很自然的,它也使用 size::MAX

usize::MAX不是 2 的幂,因此不是有效的对齐方式。 此外,LLVM 不支持超过1 << 29对齐。 并且!无论如何都不应该有大对齐,因为let x: (!, i32); x.1 = 4;应该只占用 4 个字节的堆栈,而不是千兆字节或更多。

如果您想继续讨论,请为这个讨论创建一个新线程@earthengine

这里发生的问题是Box::new(!)Box<$0> ,我们期望Box<Error> 。 有了足够的类型注释,我们需要将Box<$0>强制转换为Box<Error> ,这将导致$0默认为!

然而, $0是一个类型变量,所以没有强制转换,然后我们得到$0 = Error

解决问题的一个 hacky 选项是发现我们有一个约束$0: Sized (跟随子类型?)并插入一个以它Box键的强制转换,这样

我们已经这样做了检测Fn的倒闭,所以这不会是这样的拉伸。 还是丑。

就是这样,如果在强制期间我们遇到$0: Unsize<Y>形式的义务,其中Y绝对没有大小,而$0肯定有大小,不要将其视为歧义,而是将其视为“确定”并继续进行未调整大小的强制。

@arielb1

那么这与将Box::new(())强制转换为Box<Debug>有何不同? std::error::Error是类似于Debug ,而不是像[u8]这样的类型。 std::io::Error另一方面是一种类型,但它是Sized

我们从来没有遇到以下问题:

use std::fmt::Debug;

fn f(x:()) -> Box<Debug> {
    Box::new(x)
}

fn main() {
}

@earthengine这个问题和你的代码之间的区别是:

| 书面| Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | ------- |
| 执行 | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

本质上,因为!可以强制转换为任何东西,所以在调用Box::new之前它会被强制转换为Debug Box::new 。 这不是()的一个选项,所以在第二种情况下没有问题——那里的强制发生Box::new

@RalfJung

我的大脑模型是 DST 不能在野外存在——它们必须来自一些更具体的类型。 当我们在参数位置允许 DST 时,这甚至是正确的 - 如果不在返回位置。 也就是说,在真正的值已经被放置在一个指针之后,它不应该能够合并到一个 DST,即使它是!

例如,以下不应编译:

let v: str = !;
let v: [u8] = !;
let v: dyn Debug = !;

仅仅因为你不能用任何现有的 Rust 表达式替换!来编译它。

编辑

这不是 () 的选项

那么谁能解释一下为什么? 如果() as dyn Debug不编译,则! as dyn Debug不应编译,反之亦然。 &() as &Debug编译, &! as &Debug ,没问题。 如果() as dyn Debug可能有一天编译,我们今天遇到的问题将重复() ,因此 DST RFC 实现者将不得不使用它,因此它将解决我们遇到的相同问题为!

!强迫任何事物,因为它不可能存在。 “Ex falso quodlibet”。 所以你所有的例子都应该编译 - 没有很好的理论理由来为非大小类型设置一个特殊的例外。

这甚至不违反“DTS 不能存在”,因为!也不能存在。 :)

() as dyn Debug不编译

我不知道未调整大小的右值的计划是什么,但我猜他们可以编译这个?
关键是,为!执行此操作不需要我们实现未定义大小的右值,因为这只会发生在死代码中。

但是,也许您在这里指的是一个可能的解决方案(也许这就是上面@arielb1也建议的):是否可以将!强制限制为仅在目标类型为(已知)大小时才适用? 这样做没有很好的理论上的理由,而是一个实际的理由。 :D 也就是说,它可能有助于解决这个问题。

当我说“DTS 不能存在”时,我指的是句法上的。 例如,不可能有类型为str的局部变量。

另一方面, !不能存在是语义上的。 你可以写

let v = exit(0);

v在上下文中按语法键入! ,但由于绑定甚至不会运行,因此它不会在现实世界中退出。

所以这里的原因是:我们允许!强制转换为任何类型,仅当您可以编写具有相同类型的表达式时。 如果一个类型在语法上甚至不能存在,它就不应该被允许。

这也适用于未调整大小的右值,因此在我们拥有未调整大小的右值之前,不允许将!强制Box::new接收未确定大小的值)。

例如,不可能有类型为 str 的局部变量。

这只是当前实现的一个限制: https :

@RalfJung

这不是这里的问题。

同样,假设我们处于Box::new(!: $0): Box<dyn fmt::Debug>的情况,其中$0是一个类型变量。

然后编译器可以很容易地推断出$0: Sized (因为$0等于Box::new的类型参数,它具有T: Sized界限)。

问题在于编译器需要弄清楚使用哪种强制转换将Box<$0>转换为Box<dyn fmt::Debug> 。 从“本地”POV,有两种解决方案:

  1. 有一个CoerceUnsized强制转换,需要Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> 。 这是$0 = !的有效程序,默认设置将使其编译为该程序。
  2. 使用$0 = dyn fmt::Debug进行身份强制。 这与$0: Sized要求不一致。

编译器在考虑歧义时不想打开太多东西,因为这会导致性能问题和难以调试的问题,所以它以一种相当愚蠢的方式挑选出使用哪种强制转换(特别是,我们不没有T: CoerceUnsized<T> ,所以如果编译器选择选项 1,它很容易“卡住”),最终选择选项 2,但失败了。 我有一个想法,让它更聪明一点,然后选择选项 1。

因此,从这个 POV 考虑,正确的想法可能是触发一个无大小的强制,如果

  1. 未调整大小的强制是自洽的(这将是当前的规则,除了歧义是可以的)。
  2. 触发 unsized 强制是不一致的。 我们必须看到,这可以在不破坏较大函数性能的情况下进行计算。

进行身份强制,$0 = dyn fmt::Debug。 这与 $0:大小要求不一致。

我几乎看不出为什么我们应该允许let v: str=!而不是let v: str; 。 如果你甚至不能声明一个特定类型的变量,你为什么要绑定一个(也许不可能的)值呢?

当未发送未调整大小的右值时,一致的方法是不允许对未调整大小的类型进行任何强制,因为这会创建(可能是临时的)未调整大小的右值,即使这仅在想象中发生。

所以我的结论是Box::new(!) as Box<Error>问题是未大小右值的阻塞问题,而不是!类型的阻塞问题。 !强制规则应该是:

你可以写let v: T ,当且仅当你可以写let v: T = !

Thea 的实际含义将用语言进行评估。 特别是,在我们有未调整大小的右值之后,我们有Box::new(! as dyn Debug)但在此之前,我会说不。

那么我们可以做些什么来继续前进呢?

在触发 unsized 强制转换的代码上添加一个 feature gate,如果 unsized rvalue 是 on,尝试这个 coercion,否则跳过它。 如果这是唯一可用的强制,则错误消息应该是“不满足 trait bound str: std::marker::Sized ”,就像在类似的情况下一样。 所以

我们必须看到,这可以在不破坏较大函数性能的情况下进行计算。

已解决:只是一个简单的功能检查。

同时,为unsized rvalues添加一个新问题,并将链接添加到跟踪问题中,以确保正确解决此问题。

有趣。

这个

fn main() {
    let _:str = *"";
}

编译,但是

fn main() {
    let v:str = *"";
}

不要。 这甚至与!类型无关。 我要为此创建一个问题吗?

不知道最后一个是不是真的是bug。 第二个示例无法编译,因为在没有无大小右值支持的情况下,编译器想静态地知道要为局部变量v分配多少堆栈空间,但不能,因为它是 DST。

在第一个示例中, _模式的特殊之处在于它不仅匹配任何内容(如局部变量绑定),而且它甚至根本不创建变量。 这样的代码是一样的fn main() { *""; }let 。 取消引用一个引用(甚至 DST)然后对结果不做任何事情是没有用的,但它似乎是有效的,我不相信它应该是无效的。

对。 但是真的很混乱,尤其是下面的

fn main() {
    let _v:str = *"";
}

也不编译。 根据您关于_这应该是相同的,只是我们将未使用的东西称为_v而不仅仅是_

据我所知, _vv之间的唯一区别是前导下划线抑制了有关未使用值的警告。

另一方面, _特指“丢弃它”,并且经过特殊处理以使其能够出现在模式中的多个位置(例如元组解包、参数列表等)而不会导致关于名称冲突的错误。

另一方面, _ 特指“丢弃它”,并经过特殊处理以使其能够出现在模式中的多个位置(例如元组解包、参数列表等)而不会导致名称错误碰撞。

正确的。 除了你所说的, let _ = foo()导致drop被立即调用,而_v只有在超出范围时才会被丢弃(比如v会)。

确实,这就是我所说的“ _很特别”的意思。

因此,现在看起来在野外拒绝所有未调整大小的右值(除非打开“未调整大小的右值”功能)将是一个重大变化,尽管我认为效果很小。

然后我仍然建议有一个功能门来禁止从!到未调整大小的右值的强制转换。 这会拒绝像let _:str = return;这样的代码,但我认为没有人会在代码中使用它。

只要!类型不稳定,我们就可以对其可以或不可以强制的地方进行重大更改。

问题是,如果我们为了修复https://github.com/rust-lang/rust/issues/49593而在不强制 DST 的情况下稳定它,我们是否要在稍后添加未调整大小的右值时恢复这种强制?能够在不再次破坏https://github.com/rust-lang/rust/issues/49593 的情况下这样做吗?

我之前的提议包括这个。 #49593应该是未调整大小的右值的阻塞问题。

我个人对最终解决方案的建议是让Box::new (可能还包括其他一些重要方法)接受未定义大小的参数,并允许在类似情况下存在歧义。

FWIW, _不像一个特殊的变量名。 这是一个完全独立的模式语法,匹配的地方没有任何作用。 模式匹配适用于place ,而不是values
最接近变量绑定的是ref _x ,但即便如此,您可能仍需要 NLL 以避免由此产生的冲突借用。 这工作顺便说一句:

// The type `str` is the type of the place being matched. `x` has type `&str`.
let ref x: str = *"foo";
// Fully equivalent to this:
let x: &str = &*"foo";

@eddyb

你没有明白我的意思。 我的假设是,在 Rust 中,目前代码中根本不会出现未调整大小的右值。 然而,事实证明它们出现在let _:str = *"" 。 虽然这样的值可以暂时存在,但在语法层面上它确实存在。 像鬼一样的东西……

相反,您的示例在技术上是完全合法的,这不是重点。 str未调整大小,但&str已调整大小。

然而,事实证明它们出现在 let _:str = *"" 中。 虽然这样的值可以暂时存在,但在语法层面上它确实存在。 像鬼一样的东西……

在像&*foo这样的表达式中有一个类似的“幽灵”。 首先,您取消引用foo ,然后在不移动它的情况下获取结果的地址。 例如,它与& { *foo }

@earthengine @SimonSapin那里不存在未调整大小的右值(“值表达式”)。 只有左值(“位置表达式”)可以。 *"foo"是一个地方,模式匹配它不需要一个值,只需要一个地方。

名称“lvalue”和“rvalue”在 C 中也是遗物并且有些误导,但在 Rust 中更糟,因为let的 RHS 是一个“左值”(位置表达式),尽管名称如此。
(赋值表达式是 C 名称和规则在 Rust 中唯一有意义的地方)

在您的示例let x = *"foo"; ,需要将该值绑定到x
类似地, &*"foo"创建一个位置表达式然后借用它,而无需创建一个未定义大小的值,而{*"foo"}始终是一个值表达式,因此不允许使用未定义大小的类型。

https://github.com/rust-lang/rust/pull/52420我点击了

let Ok(b): Result<B, !> = ...;
b

不再有效。 这是故意的吗?

AFAIK 是的 - 可靠的模式故事很复杂,并且具有与!本身不同的功能门。 另见https://github.com/rust-lang/rfcs/pull/1872https://github.com/rust-lang/rust/issues/48950。

@Ericson2314特别是您需要添加到liballocexhaustive_patterns

谢谢!!

关于内部的有趣对话:

如果你打算这样做,为什么不治疗! 本身作为推理变量?

只需使用PhandomData围绕!制作一个包装器即可消除项目类型的歧义。

https://github.com/rust-lang/rust/issues/49593现在已经修复。 这就是之前恢复稳定的原因。 以前的稳定报告在这里。 让我们再试一次!

@rfcbot fcp 合并

我认为 rfcbot 可能在同一问题中支持多个 FCP。 为这轮稳定开新题吧?

https://github.com/rust-lang/rust/pull/50121不仅恢复了稳定性,还恢复了后备语义。 这是我们想要重新审视的事情吗?

问题描述中剩余的未选中复选框是:

我们应该为!实现哪些特征? 最初的 PR #35162 包括Ord和其他一些。 这可能更像是一个 T-libs 问题,所以我将这个标签添加到问题中。

我们可以稍后添加 impls,不是吗? 或者这是一个拦截器?

@SimonSapin打开https://github.com/rust-lang/rust/issues/57012。 我希望作为此更改的一部分再次启用对!的回退,是的(尽管让我们在稳定性问题上讨论)。

交叉引用: https :

显然,在特性门后面使用 never 类型存在错误/漏洞,可以在稳定版上引用它: https :

编辑:提交https://github.com/rust-lang/rust/issues/58733。

在上面链接的代码示例中添加类型的使用:

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

fn main() {
    let _a: Void;
}

这在 Rust 1.12.0 中编译,我认为这是第一个https://github.com/rust-lang/rust/pull/35162。 在 1.11.0 中,它会出错:

error: the trait bound `fn() -> !: MyTrait` is not satisfied [--explain E0277]
  --> a.rs:12:13
   |>
12 |>     let _a: Void;
   |>             ^^^^
help: the following implementations were found:
help:   <fn() -> T as MyTrait>

error: aborting due to previous error

这个是什么状态?

据我所知,自从我在https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706 中的总结以来,状态没有显着变化。

这个是什么状态?

@hosunrise :目前在https://github.com/rust-lang/rust/issues/67225 上被阻止

我可能离这里很远,但 lint 讨论 (https://github.com/rust-lang/rust/issues/66173) 提到的具体问题似乎都可以解决,如果每个枚举都有一个!分支类型系统?

我会注意到这不适用于https://github.com/rust-lang/rust/issues/67225的 OP 中提到的问题,这仍然是一个问题。

我可能离这里很远,但是如果每个枚举在类型系统中都有一个!分支,那么 lint 讨论 (#66173) 提到的具体问题似乎都可以解决?

实际上,每个枚举都可能受到威胁,因为永远不会发生的变体数量无限,因此它们都不值得一提。

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