Rust: Tracking issue for promoting `!` to a type (RFC 1216)

Created on 29 Jul 2016  Â·  259Comments  Â·  Source: rust-lang/rust

Tracking issue for rust-lang/rfcs#1216, which promotes ! to a type.

Pending issues to resolve

Interesting events and links

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

Most helpful comment

@petrochenkov Forget ! and just look at enums.

If I have an enum with two variants I can match on it with two cases:

enum Foo {
    Flim,
    Flam,
}

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

This works for any n, not just two. So if I have an enum with zero variants I can match on it with zero cases.

enum Void {
}

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

So far so good. But look what happens we try to match on nested patterns. Here's matching on a two-variant enum inside a Result.

enum Foo {
    Flim,
    Flam,
}

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

We can expand the inner pattern inside the outer one. There are two Foo variants, so there are two cases for Err. We don't need separate match statements to match on the Result and on the Foo. This works for enums with any number of variants... _except zero_.

enum Void {
}

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

Why shouldn't this work? I wouldn't call fixing this adding "special support" for uninhabited types, I'd call it fixing an inconsistency.

All 259 comments

Huzzah!

There's a WIP implementation of this here: https://github.com/canndrew/rust/tree/bang_type_coerced

It's current status is: it builds with old-trans and is usable but has a couple of failing tests. Some tests fail due to a bug that causes code like if (return) {} to crash during trans. The other tests are to do with link-time-optimization and have always been buggy for me, so I don't know if they have anything to do with my changes.

My current roadmap is:

  • Get it working with MIR. This hopefully won't be too hard as this is how I started implementing it, but I've been having a problem where MIR builds segfault during compilation.
  • Purge the obsolete divergence stuff from the compiler (FnOutput, FnDiverging and related).
  • Hide the new type behind a feature gate. This would mean, when the feature is disabled:

    • ! can only be parsed as a type in the return position.

    • Diverging type variables default to ().

  • Figure out how we can raise compatibility warnings when a defaulted () gets used to resolve a trait. One way to do this could be to add a new type to the AST called DefaultedUnit. This type behaves like () and will turn into () under some circumstances but raises a warning when it resolves a trait (as ()). The problem with this approach is I think it will be hard to catch and fix all the bugs with the implementation - we'd end up breaking people's code in order to save their code from being broken.

Is there anything that needs to be added to this list? Is it just going to be me working on this? And should this branch be moved onto the main repository?

Figure out how we can raise compatibility warnings when a defaulted () gets used to resolve a trait. One way to do this could be to add a new type to the AST called DefaultedUnit. This type behaves like () and will turn into () under some circumstances but raises a warning when it resolves a trait (as ()). The problem with this approach is I think it will be hard to catch and fix all the bugs with the implementation - we'd end up breaking people's code in order to save their code from being broken.

@eddyb, @arielb1, @anyone_else: Thoughts on this approach? I'm pretty much up to this stage (sans a couple of failing tests that I'm (very slowly) trying to fix).

What traits should we implement for !? The initial PR #35162 includes Ord and a few others.

Shouldn't ! automatically implement _all_ traits?

This kind of code is fairly common:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

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

I'd expect ! to be usable for Foo::Bar in order to indicate that a Bar can never actually exist:

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

But this is only possible if ! implements all traits.

@tomaka There's RFC about it: https://github.com/rust-lang/rfcs/pull/1637

The problem is that if ! implements Trait it also should implement !Trait...

The problem is that if ! implements Trait it also should implement !Trait...

Then special-case ! so that it ignores any trait requirement?

@tomaka ! can't automatically implement _all_ traits because traits can have static methods and associated types/consts. It can automatically implement traits that just have non-static methods though (ie. methods that take a Self).

As for !Trait, someone suggested that ! could auto-implement both Trait _and_ !Trait. I'm not sure whether that's sound but I suspect that negative traits aren't sound at all.

But yes, it might be nice if ! could auto-implement Baz in the example you gave for precisely these sorts of cases.

When exactly do we default diverging type variables to ()/! and when do we throw an error about not being able to infer enough type information? Is this specified anywhere? I'd like to be able to compile the following code:

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

But the first error I get is "unable to infer enough type information about _". In this case I think it would make sense for _ to default to !. However when I was writing tests around defaulting behaviour I found it surprisingly difficult to make a type variable default. That's why these tests are so convoluted.

I'd like to have a clear idea of exactly why we have this defaulting behaviour and when it's supposed to get invoked.

But the first error I get is "unable to infer enough type information about _". In this case I think it would make sense for _ to default to !. However when I was writing tests around defaulting behaviour I found it surprisingly difficult to make a type variable default. That's why these tests are so convoluted.

That's a very good idea in my opinion. Same for None which would default to Option<!> for example.

@carllerche

The unit_fallback test is certainly an odd way to demonstrate it. A less-macro-ey version is

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);
}

Only the type variable created by return/break/panic!() defaults to anything.

When exactly do we default diverging type variables to ()/! and when do we throw an error about not being able to infer enough type information? Is this specified anywhere?

Define "specified". :) The answer is that certain operations, which are not afaik written down anywhere outside the code, require that the type is known at that point. The most common case is field access (.f) and method dispatch (.f()), but another example is deref (*x), and there is probably one or two more. There are mostly decent reasons for this being required -- generally speaking, there are multiple diverging ways to proceed, and we can't make progress without knowing which one to take. (It would be possible, potentially, to refactor the code so that this need can be registered as a kind of "pending obligation", but it's complicated to do so.)

If you make it all the way to the end of the fn, then we run all pending trait selection operations until a steady state is reached. This is the point where defaults (e.g., i32, etc) are applied. This last part is described in the RFC talking about user-specified default type parameters (though that RFC in general needs work).

@canndrew those look a bit similar to https://github.com/rust-lang/rust/issues/12609

Boy, that's an old bug! But yes I'd say my #36038 is a dupe of that (I thought I'd seen it somewhere before). I don't think ! can really be considered for prime time until that's fixed.

Is it planned for ! to affect pattern matching exhaustiveness? Example of current, possibly-wrong behavior:

#![feature(never_type)]

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

@tikue yes, it's one of the bugs listed above.

@lfairy whoops, didn't see it because it wasn't listed in the checkboxes at the top. Thanks!

Are there plans to implement From<!> for T and Add<T> for ! (with an output type of !)? I know that's a really oddly specific request-- I'm trying to use both in this PR.

From<!> for T definitely. Add<T> for ! is probably up for the libs team to decide but I personally think ! should implement every trait that it has a logical, canonical implementation for.

@canndrew Thanks! I'm used to scala's Nothing trait which is a subtype of every type, and can thus be used pretty much anywhere a value can appear. However, I definitely sympathize with the desire to understand the effects that impl All for ! or similar would have on rust's type system, especially concerning negative trait bounds and the like.

Per https://github.com/rust-lang/rfcs/issues/1723#issuecomment-241595070 From<!> for T has coherence issues.

Ah right, yes. We need to do something about that.

It would be good if trait impls could explicitly state that they are overriden by other trait impls. Something like:

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

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

Isn't this covered by specialization? Edit: I believe this is the lattice impl rule.

Is it a viable alternative to avoid special support for uninhabited types as much as possible?

All these match (res: Res<A, !>) { Ok(a) /* No Err */ }, special methods for Result look very contrived, like features for the sake of features, and doesn't seem to worth the effort and complexity.
I understand ! is a @canndrew's pet feature and he wants to develop it further, but maybe it's a wrong direction to go from the start and #12609 is not even a bug?

@petrochenkov #12609 isn't a special feature for the Never type. It's just a bug fix to detect some clearly unreachable code.

@petrochenkov Forget ! and just look at enums.

If I have an enum with two variants I can match on it with two cases:

enum Foo {
    Flim,
    Flam,
}

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

This works for any n, not just two. So if I have an enum with zero variants I can match on it with zero cases.

enum Void {
}

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

So far so good. But look what happens we try to match on nested patterns. Here's matching on a two-variant enum inside a Result.

enum Foo {
    Flim,
    Flam,
}

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

We can expand the inner pattern inside the outer one. There are two Foo variants, so there are two cases for Err. We don't need separate match statements to match on the Result and on the Foo. This works for enums with any number of variants... _except zero_.

enum Void {
}

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

Why shouldn't this work? I wouldn't call fixing this adding "special support" for uninhabited types, I'd call it fixing an inconsistency.

@petrochenkov Maybe I misunderstood what you were saying. There's two questions discussed in the #12609 thread:

(0) Should this code be allowed to compile?

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

(1) Should this code be allowed to compile?

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

As currently implemented, the answers are "no" and "yes" respectively. #12609 talks about (1) specifically in the issue itself but I was thinking of (0) when I replied. As for what the answers _should_ be, I think (0) should definitely be "yes" but for (1) I'm not sure either.

@canndrew
It may be reasonable to make (1), i.e. unreachable patterns, a lint and not a hard error regardless of uninhabited types, RFC 1445 contains more examples of why this can be useful.

Regarding (0) I'm more or less convinced by your explanation. I'll be totally happy if this scheme lies naturally on the pattern checking implementation in the compiler and removes more special code than adds.

By the way, I've done a PR to try and fix (0) here: https://github.com/rust-lang/rust/pull/36476

I'll be totally happy if this scheme lies naturally on the pattern checking implementation in the compiler and removes more special code than adds.

It doesn't, wierdly. But that's probably more an artefact of the way I hacked it into the existing code rather than there not being an elegant way to do it.

I think for macros it's useful for (1) to not be a hard error.

I think (1) should compile by default but give a warning from the same lint as here:

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

While we're at it, does it make sense to make some patterns irrefutable in the face of !?

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

I agree that making non-exhaustive matches an error, but unreachable i.e. redundant patterns just a warning seems to make sense.

I've had some PRs sitting around for a while gathering rot. Is there anything I can do to help get these reviewed? There probably needs to be some discussion on them. I'm talking about #36476, #36449 and #36489.

I against the idea of thinking the divergent type (or "bottom" type in type theory) is the same as the empty enum type (or "zero" type in type theory). They are different creatures, although both cannot have any value or instance.

In my opinion, a bottom type can only appear in any context that represents a return type. For example,

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

But you should not say

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

or

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

or even

type ID=fn(!)->!;

as no variables should have type !, and so no input variables should have type !.

Empty enum is different in this case, you can say

enum Empty {}

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

then

 match Empty::new() {}

This is to say, there is a fundamental difference between ! and Empty: you cannot declare any variable to have type ! but can do so for Empty.

@earthengine What is the advantage of introducing this (in my eyes completely artificial) distinction between two kinds of uninhabitable types?

Numerous reasons for not having such a distinction have been brought up - for example, being able to write Result<!, E>, have this interact nicely with divergent functions, while still being able to use the monadic operations on Result, like map and map_err.

In functional programming, the empty type (zero) is frequently used to encode the fact that a function does not return or that a value does not exist. When you say bottom type, it's not clear to me which concept of type theory you are referring to; usually bottom is the name for a type that's a subtype of all types -- but in that sense, neither ! not an empty enum are bottom in Rust. But again, zero not being a bottom type is not uncommon in type theory.

This is to say, there is a fundamental difference between ! and Empty: you cannot declare any variable to have type ! but can do so for Empty.

That's what this RFC is fixing. ! is not really a "type" if you can't use it like a type.

@RalfJung
Those concepts are from linear logic https://en.wikipedia.org/wiki/Linear_logic

As there is some mistakes in the previous text in this post, I removed them. Once I get it right will update this.

top is a value can be used in any possible ways

What does this mean in subtyping? That it can become any type? Because that's bottom then.

@eddyb I made some mistakes, please wait for my new updates.

This PR - which got merged in a day - conflicts pretty badly with my pattern matching PR which has been sitting in review for over a month. Sorry, my second pattern matching PR since the first one became unmergeable and had to be rewritten after it sat in review for two months.

ffs guys

@canndrew argh, I'm sorry about that. =( I was planning on r+'ing your PR today, too...

Sorry, I don't want to whine but this has hella been dragging on. I gave up on trying to maintain multiple PRs at once and now I've been trying to get this one change in since September. I think part of the problem is the timezone difference - you lot are all online while I'm in bed so it's hard to chat about this stuff.

Given that exceptions are a logical and necessary hole in the type system, I was wondering if anybody has given serious thoughts to moving towards more formally handling them with !.

Using https://is.gd/4EC1Dk as an example and reference point, what if we went past that, and.

1) Treat any function that can panic but doesn't return an error or result have it's type signature implicitly change from -> Foo to -> Result<Foo,!> 2) anyResulttypes would have their Error types implicitly be converted toenum AnonMyErrWrapper { Die(!),Error}```
3) Since ! is zero sized ad unhinhabitable, There would be zero cost to converting between the types, and implicit conversion could be added to make it backwards compatible.

The benefit, of course, being that exceptions are effectively lifted into the type system, and it would be possible to reason about them, perform static analysis on them, etc.

I realize that this would be ...non-trivial from a community point of view, if not from a technical one. :)

Also, this probably overlaps with a possible future effects system.

@tupshin that's a breaking change, at least without a lot of gymnastics. I recommend disabling unwinding and using "Result" manually if you want that clarity. [And btw, this issue isn't really the place to bring up such a thing---the design of ! is not something you are calling into question so this is purely future work.]

I realize how breaking it would be, at least without significant gymnastics, and fair point about it being totally future work. And yes, I'm quite happy with what is planned for ! so far, as far as it goes. :)

@nikomatsakis Regarding the pending issues, these two can be ticked off

  • Code cleanup from #35162, re-organize typeck a bit to thread types through return position instead of calling expr_ty
  • Resolve treatment of uninhabited types in matches

I'm going to start another crack at this one:

  • How to implement warnings for people relying on (): Trait fallback where that behavior might change in the future?

I'm planning on adding a flag to TyTuple to indicate that it was created by defaulting a diverging type variable, then checking for that flag in trait selection.

@canndrew

I'm planning on adding a flag to TyTuple to indicate that it was created by defaulting a diverging type variable, then checking for that flag in trait selection.

OK, great!

Well, maybe great. =) Sounds a bit complex to have two semantically equivalent types (() vs ()) that are distinct in that way, but I can't think of a better way. =( And a lot of the code ought to be prepared for it anyhow thanks to regions.

What I mean is I'm adding a bool to TyTuple

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

which indicates that we should raise warnings on this (unit) tuple if we try to select certain traits on it. This is safer than my original approach of adding another TyDefaultedUnit.

Hopefully we only need to keep that bool around for one warning cycle.

Regarding the uninitialized/transmute/MaybeUninitialized issue I think a sensible course of action would be:

  • Add MaybeUninitialized to the standard library, under mem
  • Add a check on uninitialized that it's type is known to be inhabited. Raise a warning otherwise with a note saying that this will be a hard error in the future. Suggest using MaybeUninitialized instead.
  • Add a check on transmute that it's "to" type is only uninhabited if it's "from" type is aswell. Similarly raise a warning which will become a hard error.

Thoughts?

There appears to be some disagreement about the semantics of &!. After #39151, with the never_type feature gate enabled it is treated as uninhabited in matches.

If automatic trait impls for ! won't be there, at least implementing appropriate traits from std would be very helpful. One huge limitation of void::Void is that it lives outside of std and that means blanket impl<T> From<Void> for T is impossible and that limits error handling.

I think at least From<!> should be implemented for all types and Error, Display and Debug should be implemented for !.

I think at least From<!> should be implemented for all types

This unfortunately conflicts with the From<T> for T impl.

This unfortunately conflicts with the From<T> for T impl.

At least until lattice impl specialization is implemented.

Good points. I hope to see it done one day.

Should [T; 0] subtype a [!; 0] and &[T] subtype a &[!]? It seems intuitive to me that the answer ought to be “yes”, but the current implementation thinks otherwise.

[!; 0] is inhabited and so is &[!] so I'd say no. Besides, we're not using subtyping this time around.

Both [!; 0] and &[!] shouldn't be uninhabited, both types can accept the value [] (or &[]).

Nobody said they are or should be uninhabited.

let _: u32 = unsafe { mem::transmute(return) };
In principle this could be OK, but the compiler complains "transmuting between 0 bits and 32 bits".

Transmuting ! -> () is allowed however. I don't have any particular reason to point this out; it's an inconsistency, but I can't think of a practical or theoretical problem with it.

I would not expect subtyping and would be opposed to it on the grounds of avoiding complicating all manner of logic in the compiler. I do not want subtyping that is unrelated to regions or region binding, basically.

Is it possible to implement Fn, FnMut and FnOnce for ! in a way that it is generic over all input and output types?

In my case I have a generic builder which can take a function, which will be invoked when building:

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

As func is an Option, a constructor using None can't infer the type of F. Therefore a fn new implementation should use Bang:

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

The builder's func function should look something like this to be invoked both on Builder<!> and Builder<F>:

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

Now the Problem: Currently the Fn* traits are not implemented for !. Additionally, you can't have a generic associated type for traits (e.g. Output in Fn). So is it possible to implement the Fn* trait family for ! in a way that it is generic over all input and output types nevertheless?

That sounds contradictory. Doesn’t <! as Fn>::Output need to resolve to a single type?

<! as Fn>::Output is !, isn't it?

In theory <! as Fn>::Output should be !, but the current type checker wants associated types to match exactly: https://is.gd/4Mkxfm

@SimonSapin It does need to resolve to a single type, which is why I raise my question. From a language point of view it's completely correct behaviour. But from a library point of view the usage of ! in contexts of generic functions would be very limited if the current behaviour is kept.

Should the following code

#![feature(never_type)]

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


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

prints 10? Right now it does not print anything. Or any other workarounds that will delay the return until the print?

I mean, as I said before, there is actually two different kinds of ! type: one is lazy and one is eager. The usual notation denotes an eager one, but sometime we might need the lazy one.


As @RalfJung is interested in a previous version that uses &return, I adjusted the code again. And again, My point is we should be able to delay the return later. We could use closures here but I can only use return, not break or continue etc.

For example, it would be good if we could write

#![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);
    }
}

with the break delayed until 5 being printed.

@earthengine No-- that should most definitely not print anything. ! is uninhabited, which means that no values of type ! can be created. So if you have a reference to a value of type !, you're inside of a dead code block.

The reason that return has type ! is because it diverges. Code that uses the result of a return expression can never be executed because return never "finishes".

oO I did not know you can write &return. That makes no sense, why should you be allowed to take the address of return?^^

But, anyway, @earthengine in your code, arguments are evaluated before quit_with_print is called. Evaluating the 2nd argument runs return, which terminates the program. This is like writing something like foo({ return; 2 }) -- foo is never executed.

@RalfJung return is an expression of type !, just like break or continue. It has a type, but its type is uninhabited.

! has been implemented and working in nightly for a year now. I'd like to know what needs to be done to move it towards stabilization.

One thing that hasn't been resolved is what to do about the intrinsics that can return ! (ie. uninitialized, ptr::read and transmute). For uninitialized, last I heard there was a consensus that it should be deprecated in favor of a MaybeUninit type and possible future &in, &out, &uninit references. There's no RFC for this, although there is an earlier RFC where I proposed deprecating uninitialized just for types that don't implement a new Inhabited trait. Perhaps that RFC should be scrapped and replaced with "deprecate uninitialized" and "add MaybeUninit" RFCs?

For ptr::read, I think it's fine to leave it as UB. When someone calls ptr::read they're asserting that the data they're reading is valid, and in the case of ! it's definitely not. Maybe someone has a more nuanced opinion on this though?

Fixing transmute is easy - just make it an error to transmute to an uninhabited type (instead of ICEing as it currently does). There was a PR to fix this but it was closed with the reason that we still need a better idea of how to treat uninitialized data.

So where are we at with these at the moment? (/cc @nikomatsakis)? Would we be ready to move forward with deprecating uninitialized and adding a MaybeUninit type? If we made these intrinsics panic when called with !, would that be a suitable stop-gap measure which would allow us to stabilise !?

There's also the pending issues listed:

What traits should we implement for !? The initial PR #35162 includes Ord and a few others. This is probably more of a T-libs issue, so I'm adding that tag to the issue.

Currently there's a fairly basic selection: PartialEq, Eq, PartialOrd, Ord, Debug, Display, Error. Besides Clone, which should definitely be added to that list, I can't see any others that are vitally important. Do we need to block stabilization on this? We could just add more impls later if we see fit.

How to implement warnings for people relying on (): Trait fallback where that behavior might change in the future?

The resolve_trait_on_defaulted_unit warning is implemented and already in stable.

Desired semantics for ! in coercion (#40800)
Which type variables should fallback to ! (#40801)

These seem like the real blockers. @nikomatsakis: Do you have any concrete ideas about what to do about these that are just waiting to be implemented?

Does it make sense for ! to also implement Sync and Send? There are several cases where error types have Sync and/or Send bounds, where it would be useful to use ! as "can't fail".

@canndrew I disagree with forbidding it in unsafe code. The unreachable crate uses transmute to create uninhabited type and so hint optimizer that certain branch of code can never occur. This is useful for optimizations. I plan to make my own crate using such technique in interesting way.

@Kixunil wouldn't it be more explicit to use https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html here?

@RalfJung it would, but that is unstable. Reminds me that forbidding transmuting into uninhabited types would be a breaking change.

@jsgf Sorry, yes, ! also impls all the marker traits (Send, Sync, Copy, Sized).

@Kixunil Hmm, that's a shame. It would be much better to stabilize the unreachable intrinsic though.

Not a stabilization-blocker (because perf, not semantics), but it seems that Result<T, !> isn't using the uninhabitedness of ! in enum layout or match codegen: https://github.com/rust-lang/rust/issues/43278

This is such a vague idea that I almost feel guilty for dropping it here, but:

Could chalk possibly help answer some of the tougher questions with regards to !: Trait implementations?

@ExpHP I don't think so. Auto-implementing !: Trait for traits which don't have associated types/consts and only non-static methods is known to be sound. It's just a matter of whether we want to do it or not. Auto-implementing for other traits doesn't really make sense because the compiler would need to insert arbitrary values for the associated types/consts and invent it's own arbitrary static method impls.

There is already a resolve-trait-on-defaulted-unit lint in rustc. Should the corresponding item be ticked off?

@canndrew Is it unsound to set associated types to !?

@taralx it's not unsound, but it would prevent valid code from working. Note that the following code compiles today:

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

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

@lfairy It depends on your opinion on what is "valid" code. For me, I would happily accept that the code you given to be "invalid", as you should not be able to get anything from !.

@earthengine I don't see the point you're trying to make. You can call a static method on a type without having an instance of it. Whether or not you can "get anything" from ! has nothing to do with that.

There's no reason why you shouldn't be able to get anything from ! the type. You can't get anything from a ! though, which is why non-static methods on ! could be derived without forcing any decisions on the programmer.

I think the easiest to remember rule would be implementing any trait for which there there is exactly 1 valid implementation (excluding those that add more divergence than !s in scope already cause).

Anything we go with should be equal or more conservative with that (which no automatic impls also fits).

@Ericson2314 I don’t understand what that means, could you give some examples?

@SimonSapin The "no more divergence" rule means no panic!() or loop { }, but a v: ! that is already in scope is fine. With expressions like those ruled out, many traits only have one possible impl for ! that type-checks. (Others may have an informal contract which rules out all but 1 impl, but we can't deal with those automatically.)

// 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 }

Mathematically speaking, ! is an "initial element", which means that for a type signature that has ! in its arguments, there is exactly one implementation, i.e., all implementations are equal -- when observed from the outside. This is because they all cannot be called.

What's actually blocking this from stabilization? Is it only the mem::uninitialized situation or something else?

@arielb1: I think it's #40800 and #40801 aswell. Do you know what the status is on these?

What docs have been written on this, or are people planning to write for this? With the recent additions to the primitive type pages in the std docs (#43529, #43560), should the ! type get a page there? The reference should probably also get updated, if it hasn't already.

Are there any plans to allow specifiying that asm is diverging so we can write things like?

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

Without having to use loop{} to appease the type checker?

Edit: Although I suppose using core::intrinsics::unreachable may be acceptable in very low level code.

@Eroc33 when ! is a type, you can do this:

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

Until then you can do this:

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

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

@Kixunil ! is a type on nightly which you need to use asm!. This is usually done as:

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

@Kixunil It can be done more easily using std::intrinics::unreachable() which exactly specifies that the code before is diverging.

@Kixunil From the above discussion std::mem::uninitialized being able to return ! appears to be subject to change though? Unless I have misunderstood that?

From the above discussion std::mem::uninitialized being able to return ! appears to be subject to change though? Unless I have misunderstood that?

I think uninitialized will eventually be deprecated entirely and, as a stop gap, will be made to panic at runtime when used with ! (though that wouldn't effect this case).

I also think we'll need to stabilize std::intrinsics::unreachable somewhere in libcore and name it unchecked_unreachable or something to distinguish it from the unreachable! macro. I've been meaning to write an RFC for that.

@eddyb Ah, yeah, forgot that asm!() is unstable, so std::intrinsics::unreachable() might be used as well.

@tbu- Sure, I highly prefer that one instead of creating uninhabited type. The issue is, it's unstable, so if it was somehow impossible to unsafely mark branch of code as unreachable, it'd not only break existing code, but also make it unfixable. I think such thing would severely harm Rust reputation (especially considering main developers proudly claiming it being stable). As much as I love Rust, such thing would make me reconsider it being useful language.

@Eroc33 my understanding is that some people suggest doing it, but it's mere suggestion, not definite decision. And I stand against such suggestion because it'd break backward compatibility, forcing Rust to become 2.0. I don't think there is any harm supporting it. If people are concerned about bugs, lint would be more appropriate.

@canndrew Why do you think uninitialized will be deprecated? I can't believe such thing. I find it extremely useful, so unless there exists a very good replacement, I don't see any reason for doing so.

Finally, I'd like to reiterate that I agree that intrinsics::unreachable() is better than current hacks. That being said, I oppose forbidding or even deprecating those hacks until there is a sufficient replacement.

Replacement for uninitialised is union { !, T }. You can get unreachable by
ptr::read::<*const !>()ing and many other similar ways.

On Aug 8, 2017 3:58 PM, "Martin Habovštiak" notifications@github.com
wrote:

@eddyb https://github.com/eddyb Ah, yeah, forgot that asm!() is
unstable, so std::intrinsics::unreachable() might be used as well.

@tbu- https://github.com/tbu- Sure, I highly prefer that one instead of
creating uninhabited type. The issue is, it's unstable, so if it was
somehow impossible to unsafely mark branch of code as unreachable, it'd not
only break existing code, but also make it unfixable. I think such thing
would severely harm Rust reputation (especially considering main developers
proudly claiming it being stable). As much as I love Rust, such thing would
make me reconsider it being useful language.

@Eroc33 https://github.com/eroc33 my understanding is that some people
suggest doing it, but it's mere suggestion, not definite decision. And I
stand against such suggestion because it'd break backward compatibility,
forcing Rust to become 2.0. I don't think there is any harm supporting it.
If people are concerned about bugs, lint would be more appropriate.

@canndrew https://github.com/canndrew Why do you think uninitialized
will be deprecated? I can't believe such thing. I find it extremely useful,
so unless there exists a very good replacement, I don't see any reason for
doing so.

Finally, I'd like to reiterate that I agree that intrinsics::unreachable()
is better than current hacks. That being said, I oppose forbidding or even
deprecating those hacks until there is a sufficient replacement.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-320948013,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AApc0iEK3vInreO03Bt6L3EAByBHQCv9ks5sWFt3gaJpZM4JYi9D
.

@nagisa Thanks! Seems like it'd solve the problem on technical level.

Would it be possible to carve out a subset of this so that ! could be used as a type parameter in return types? Right now if you have a stable function

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

And want to use ?, you can't make the usual transformation to

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

Do the thorny coercion and type parameter default questions affect that case?

40801 Can be ticked off.

We should try to fix #43061 before we stabilize !.

I didn't see any open I-unsound issues mentioning never_type so I filed a new one for this: #47563. Things seem to assume that [!; 0] is uninhabited but I can create one with a simple [].

@dtolnay added to issue header

@canndrew how are you for time? I'd like to see a subset of ! usage get stabilized (excluding the rules around what is exhaustive). I've kind of lost the thread of where we are, though, and I think it needs a champion. Do you have time to be that person?

@nikomatsakis I'm sure I can find some time to work on this stuff. What needs to be done exactly though? Is it just the bugs linked on this issue?

It looks like @varkor already has PRs open to fix the remaining issues (awesome!). As far as I can see, the only things left to do are to decide if we're happy with the traits that are currently implemented and to move/rename the feature gate so that it only covers the exhaustive pattern-matching changes.

Although another thing: Do we want to make mem::uninitialized::<!>() panic at run-time (it currently causes UB)? Or should we leave those sorts of changes for now? I'm not up-to-speed with what's going on with the unsafe code guidelines stuff.

I think the plan is still https://github.com/rust-lang/rfcs/pull/1892, which I just noticed you wrote. :)

@RalfJung Is there anything in particular blocking that? I could write a PR today which adds MaybeUninit and deprecates uninitialized.

@canndrew Since many projects want to support all three release channels and uninitialized is available on stable, it’s preferable to only start emitting deprecation warnings on Nightly once the replacement is available on the stable channel. Soft deprecation through doc-comments is fine.

I've created a PR for stabilizing !: https://github.com/rust-lang/rust/pull/47630. I dunno if we're ready to merge it yet. We should at least wait to see if @varkor's PRs fix the remaining open issues.

I also think we should revisit https://github.com/rust-lang/rfcs/pull/1699 so that people can start writing elegant trait impls for !.

@canndrew: Although that RFC wasn't accepted, it looks remarkably similar to the proposal in https://github.com/rust-lang/rust/issues/20021.

https://github.com/rust-lang/rust/issues/36479 should probably be added to the issue header aswell.

@varkor it is very similar. With your unreachable code work have you noticed any problems with code like this now being flagged as unreachable?:

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

Because that would pretty much necessitate accepting some kind of proposal like those.

@canndrew something I've been advocating lately -- though we've yet to develop a formal template -- is a kind of "summary issue" that attempts to clearly delineate what we are stabilizing. You can think of it as a report after the RFC, trying to summarize in sort of a bullet-list form the salient features of what is being stabilize but also what is not.

Part of this would be pointers to test cases that demonstrate the behavior in question.

Do you think you could try to draw up such a thing? We can chat a bit together if you like about a kind of "outline" -- or I can maybe try to sketch it out.

Related question: should we try to resolve https://github.com/rust-lang/rust/issues/46325 before we stabilize? Maybe it doesn't matter.

@nikomatsakis I vote for not to wait for any warning only issues to be resolved. That is harmless. If no further real concerns appeared, I think we should go ahead and stabilize it.

@canndrew: I don't think I've really looked at any cases like that, though I definitely think being able to omit impossible implementations will be much-needed when ! is stabilised.

@nikomatsakis

Do you think you could try to draw up such a thing? We can chat a bit together if you like about a kind of "outline" -- or I can maybe try to sketch it out.

I could write up a draft at least and you could tell me if it's what you had in mind. I'll try to get it done over the next couple of days.

@nikomatsakis Something like this?

Summary issue - stabilization of !

What is being stabilized

  • ! is now a full-fledged type and can now be used in any type position (eg. RFC 1216). The ! type can coerce into any other type, see https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs for an example.
  • Type inference will now default unconstrained type variables to ! instead of (). The resolve_trait_on_defaulted_unit lint has been retired. An example of where this comes up is if you have something like:

    // 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);
    

    Under the old rules this would deserialize a (), whereas under the new rules it will deserialize a !.

  • The never_type feature gate is stable, although some of the behaviours it used to gate now live behind the new exhaustive_patterns feature gate (see below).

What is not being stabilized

should we try to resolve #46325 before we stabilize?

As nice as it would be to clean up every loose end like this, it doesn't really seem like a blocker.

@canndrew

Something like this?

Yep, thanks! That's great.

The main thing that is missing are pointers to test cases showing how ! behaves. The audience should be the lang team or other people following closely, I would think, it's not really targeting "general purpose" folks. So e.g. I'd like some examples of places where coercions are legal, or where ! can be used. I'd also like to see the testes that show us that exhaustive pattern matching (without feature gate enabled) still behaves. These should be pointers into the repository.

@canndrew

As nice as it would be to clean up every loose end like this, it doesn't really seem like a blocker.

Well, it means that we'll enable new code that is going to immediately change behavior (eg., let x: ! = ... will I think behave differently for some expressions). Seems best to resolve if we can. Maybe you can make it a hard error on the PR you had open and we can lump it in with the existing crater run on the PR?

@nikomatsakis I've updated that summary issue again with some links and examples. Also, sorry it's taken me a while to get to this, I've been very busy this past week.

Maybe you can make it a hard error on the PR you had open and we can lump it in with the existing crater run on the PR?

Done.

@rfcbot fcp merge

I propose that we stabilize the ! type -- or least part of it, as described here. The PR is here.

One bit of data I would like is a crater run. I am in the process of rebasing https://github.com/rust-lang/rust/pull/47630 (since @canndrew isn't responding to pings just now) so we can get that data.

Team member @nikomatsakis has proposed to merge this. The next step is review by the rest of the tagged teams:

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

No concerns currently listed.

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

Oh, a couple of things I just remembered:

  • We should consider the idea of stabilizing this only in the new epoch. In particular, the change to fallback rules are backwards incompatible -- the crater run will give us some kind of lower bound on the fallout, but it's only a lower bound. But maybe we can just keep the old fallback rules unless you are in the new epoch.
  • Second, I believe that part of the plan here was also to have some guidelines for when it is appropriate to implement a trait for !. The TL;DR is that it is ok if the methods in the trait are unusable without first supplying a ! value -- so implementing Clone for ! is ok, I think, but implementing Default is not. Put another way, if implementing the impl is going to require you to panic! indiscriminately, that's a bad sign. =)

@nikomatsakis Could we change the fallback rule in a new epoch but still make ! as a type available in the 2015 epoch?

@nikomatsakis

We should consider the idea of stabilizing this only in the new epoch.

Last time we did a crater run (which was a long time ago) the fallout from changing the fallback rules was fairly minimal. We've also been linting against code that could be effected by the change for a while.

Second, I believe that part of the plan here was also to have some guidelines for when it is appropriate to implement a trait for !.

This is mentioned in the docs for !

@SimonSapin

Could we change the fallback rule in a new epoch but still make ! as a type available in the 2015 epoch?

Yes

@canndrew

Last time we did a crater run (which was a long time ago) the fallout from changing the fallback rules was fairly minimal. We've also been linting against code that could be effected by the change for a while.

Yep. Let's see what crater says. But, as I said, crater only ever gives us a lower bound -- and this is kind of an "elective change". Still, I suspect you are right and we can "get away" with changing this without much effect on code in the wild.

The TL;DR is that it is ok if the methods in the trait are unusable without first supplying a ! value

That's just the normal rule - you add an impl when you can implement it in a sane way, where "implement it in a sane way" excludes panicking, but includes "ex falso" UB in the presence of invalid data.

@arielb1 Yes, but for some reason people tend to get confused about things like this in the presence of !, so it seems worth calling out explicitly.

Maybe it'd help to have a safe method fn absurd(x: !) -> ! that's documented to be a safe way to express unreachable code or so somewhere in libstd? I think there was an RFC for that... or at least an RFC issue: https://github.com/rust-lang/rfcs/issues/1385

@RalfJung Isn’t that absurd function just identity? Why is it useful?

It’s not the same as the unsafe fn unreachable() -> ! intrinsic which doesn’t take any argument and is very undefined-behaviory.

Well, yeah, it mostly is. I was using the ! return type in the sense of "does not terminate". (The usual type of absurd is fn absurd<T>(x: !) -> T, but that doesn't seem useful in Rust either.)

I was thinking maybe this would help with "people tend to get confused about things like this in the presence of !" -- having some documentation somewhere that "ex falso" reasoning is a thing.

Seems I also got the wrong issue... I thought somewhere there was a discussion about such an "ex falso" function. Seems like I'm wrong.

fn absurd<T>(x: !) -> T can also be written match x {}, but that’s hard to come up with if you haven’t seen it before. It’s at least worth pointing out in rustdoc and in the book.

fn absurd(x: !) -> T can also be written match x {}, but that’s hard to come up with if you haven’t seen it before.

Right, that's why I suggested a method somewhere in libstd. Not sure where the best place is though.

The nice thing about absurd is that you can pass it to higher-order functions. I'm not sure this is ever necessary given how ! behaves wrt. unification, though.

fn absurd<T>(x: !) -> T can also be written match x {}

It can also be written as just x (AFAIK) - you only need match if you have an empty enum.

An absurd(x: !) -> T function would be useful for passing to higher-order functions. I've needed this (for instance) when chaining futures, one of which has error type ! and the other error type T. Even though an expression of type ! can coerce into T that doesn't mean that ! and T will unify, so sometimes you still need to manually convert the type.

Similarly, I think Result-like types should have an .infallible() method which converts Result<T, !> to Result<T, E>.

Result-like types should have an .infallible() method which converts Result to Result.

I would have expected such a method to have type Result<T, !> -> T.

I would have expected such a method to have type Result -> T.

Isn't this just the same as unwrap? The panic will be optimized away since the Err case is unreachable.

@Amanieu The point is that there's no panic to begin with. I'd love to have a panic lint, and that would get tripped up if we relied on optimization. Even without the lint, using unwrap adds a footgun during refactors, less the panic become live code once again.

unwrap reads like assert -- "run-time check ahead" (IIRC there were even proposals to rename it but they came too late... sadly). If you don't want and need a run-time check, infallible would make that explicit.

:bell: This is now entering its final comment period, as per the review above. :bell:

@rfcbot fcp cancel

@aturon and @pnkfelix have not checked their boxes.

@cramertj proposal cancelled.

Team member @cramertj has proposed to merge this. The next step is review by the rest of the tagged teams:

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

No concerns currently listed.

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

:bell: This is now entering its final comment period, as per the review above. :bell:

We discussed this in the meeting today. I remain torn about whether to change the fallback now or only in the new epoch. Here are the various points.

Expected impact of the change is small. So realistically this doesn't make much difference either way. You can see the full analysis here, but the summary is:

And that just leaves the two actual regressions: oplog-0.2.0 (actually it's its dependency bson-0.3.2 which is broken) and rspec-1.0.0-beta.4. Both of these are relying on the old fallback behaviour, though luckily they're breaking at compile time, not run time. I'll submit PRs to fix these crates.

But it is still technically a breaking change (and a voluntary one). There is no particular reason that we have to change the fallback for type variables, except that () is a singularly poor choice and it's pretty rare for code to rely on it. We've also been warning about this for a long time now.

I think this is more a question of philosophy: in the past, we've made minor breaking changes like this out of necessity. But now that we have the epochal mechanism, it offers us a more principled way to make such transitions without technically breaking anything. It may be worth putting it to use, just out of principle. On the other hand, that would mean sometimes waiting for years to make a change like this. And of course we have to maintain both versions in perpetuity, making the language spec etc that much more complex.

I'm a bit torn but I think on balance, having teetered back and forth, I'm currently leaning towards "let's just change it universally", as planned.

I know I'm biased, but I see changing the fallback behaviour as more of a bugfix compared to stuff like dyn Trait and catch.

Furthermore, if someone wants to jump on this and say, "Ah ha! Rust breaks backwards compatibility after all! Their promise of stability when it hit 1.0 was a lie!" then the very thoughtful discussion on this issue shows that the decision wasn't made lightly, even given that there was negligible practical impact.

OK, I say let's just change it.

As a compromise, then, I would like to give a helpful note for errors that informs people that the behavior has changed -- seems plausible. The idea would something like this:

  • If we see an error like !: Foo for some trait, and that trait is implemented for (): Foo, and we have done fallback (we can tell the error-reporting code this fact), then we add an extra node.

The final comment period is now complete.

Are those pending check boxes in the top post just not checked yet? Are there anything undone in that list?

@earthengine I don't think the two I see are especially relevant -- as for the top item, about the set of impls, I guess that is now decided for the time being.

Basically to be a very minimal set.

@nikomatsakis I just created the summary issue here: https://github.com/rust-lang/rust/issues/48950

Has there been any consideration of making ! a pattern that matches values of the ! type? (I have done a quick grep through this issue and the original RFC issue, but didn't find anything relevant).

I would find this useful for types such as StreamFuture which create composite error types from potentially ! error types. By using a ! pattern in map_err, if the error type at some point in the future changes, then the map_err call would stop compiling instead of potentially doing something different.

As an example, given

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

this would allow writing the more explicit

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

instead of the potentially error-prone

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

or the slightly less error-prone

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

EDIT: Re-reading https://github.com/rust-lang/rfcs/pull/1872 the comments do mention introducing the ! pattern. That's purely in the context of match expressions though, I'm not sure whether it generalizes to closure/function arguments.

@Nemo157

Has there been any consideration of making ! a pattern that matches values of the ! type?

Interesting. I think the plan was just to let you leave off such arms, in which case -- if the type were to change in the future -- you would still get an error, because your match would now not no longer be exhaustive. The problem with a ! pattern is that, if it matches, it means the arm is impossible, which is a bit awkward; we'd want to account for that, I imagine, by not requiring you to give an expression to execute. Still, it might be nice to have a way to say explicitly that this case cannot happen.

Actually, extending the unreachable pattern handling could be an alternative way to solve this. If the match arm was provably impossible, maybe the code inside the arm could be ignored. Then when you have

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

this would still expand as today (_I'm not 100% certain this is the correct expansion, but seems close enough_)

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)),
    })
}

but the Result::Err branch would not be type checked and you would not get the the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied error you get today.

I don't think that is compatible with what RFC 1872 was proposing though, because there is an explicit arm that is performing a shallow match, it is not necessarily relying on the validity of the ! value, and could potentially run the associated branch "safely" as long as that branch does not use e.0 at all.

! is a positive type, like bool, so it's kinda impossible to pattern-match on it in a closure argument list without extending the closure syntax to allow multiple branches. eg. maybe we could allow closures bool -> u32 to be written something like (ugly hypothetical syntax) [~ |false| 23, |true| 45 ~] -> u32, then a closure from any empty type to u32 could simply be written [~ ~] -> u32.

w.r.t your original examples, you can write foo.map_err(|_: (!, _)| unreachable!()) though I prefer foo.map_err(|(e, _)| e) since it avoids using unreachable!.

! is a positive type

What do you mean by positive type?

it's kinda impossible to pattern-match on it in a closure argument list without extending the closure syntax to allow multiple branches

Argument lists already support irrefutable sub-patterns, for example

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

I would just consider the ! pattern as an irrefutable pattern that matches all 0 values of the ! type, the same as () is an irrefutable pattern that matches all 1 values of the () type.

I would just consider the ! pattern as an irrefutable pattern that matches all 0 values of the ! type, the same as () is an irrefutable pattern that matches all 1 values of the () type.

Well, but 0 != 1. ;) It's kind of pointless to even write any code after matching on a ! -- so e.g. if the argument type is (!, i32), then |(!, x)| code_goes_here is silly as that code is dead anyway. I would probably write |(x, _)| match x {} to make it very explicit that x is an element of !. Or, using the absurd function I proposed above, |(x, _)| absurd(x). Or maybe |(x, _)| x.absurd()?

One could imagine a different syntax that lets one write no code, like what @canndrew wrote above. match has any number of branches, so it is in particular possible to have no branch -- but closures have exactly one case, so the only patterns that make sense are those that have exactly 1 way to match. Not 0 ways.

Too bad we can’t add impl<T> From<!> for T now. (It overlaps with From<T> for T, unless something something specialization?) We probably can’t add it later. (In release cycles after ! has been stabilized, some crate might implement From<!> for SomeConcreteType.)

@SimonSapin good point! This is very useful building block for many things, and I'd hate to loose it for ever. I'd seriously consider a one-off hack to allow both instances. They coincide semantically in the overlapping case, so there's no "operational incoherence".

I'd seriously consider a one-off hack

Then that hack needs to land in the next few days. (Or be backported during beta.)

@SimonSapin How quickly/easily could such a hack be added? It would really suck to never be able to have From<!> for T.

Alternatively, we could quickly throw on a warning against implementing From<!> for SomeConcreteType.

I don’t know, depends what the hack is. I think trait specialization can enable two overlapping impls if there’s a third for the intersection, but does that requires the trait to be publicly “specializable”?

Unfortunately, specialization:

  • Would require From::from to be changed to a default fn, which would be “visible” outside of std. I don’t know if we want to do that in std traits as long as specialization is unstable.
  • As accepted in RFC 1210, specifically does not support the language feature I was thinking of (disambiguating two overlapping impls where neither is more specific than the other by writing a third impl for the intersection).

Here the two impls do the exact same thing. If we just hack turn off the coherence check it will be hard to determine which impl gets used, which is normally wildly unsound, but in this case fine. Might trip some panics in compile expecting e.g. one result but we can hack around that.

In other words, I think this is wayyyyy easier than expediting any of specialization, thankfully.

Is there an existing RFC for formalizing this "allow arbitrary overlap if all impls involved are unreachable" idea? Seems like an interesting approach to consider.

Not that I know of. To be pendantic, the impl itself isn't unreachable, but rather the methods inside are all uncallable (including no associated types or constants as those are always "callable"). I suppose I could quickly write one if it is deemed the hack is blocked on such a thing.

Just mentioning issue #49593 that might cause ! to get un-stabilized again for better findability.

I wonder what other impls besides T: From<!> can not be added after ! is stabilized. We should probably try to get a handle on all such reasonable impls before stabilizing, as distinct from impls for ! in general, where there is no such rush.

Cross-posting from this comment:

I was wondering -- what is the classic example of why fallback to () would "have" to be changed? That is, if we continued to fallback to (), but still added !, it would certainly avoid that sort of problem -- and falling back to ! is not optimal, given that there are cases where the fallback occurs to a type variable that winds up influencing "live" code (the intention of course is otherwise, but it's hard to prevent leaks, as we've found).

There have been several regressions as a result of that change and I have to say that I am not altogether comfortable with it:

(Nominating for lang-team discussion around this point.)

During the meeting, we retreated a bit some of the options here -- I am feeling reluctant to make changes, mostly because it changes the semantics of existing code in some corner cases, and it's not clear that the new rules are better. But mostly I think we decided that it would be great to try and collect the pros/cons of each design again so we can have a more complete discussion. I'd like to see a list of use cases as well as pitfalls. @cramertj mentioned their desire for fallback in variant cases -- e.g., Ok(33) having a type that falls back to Result<i32, !>, instead of erroring as it would do today; I think they were then saying that keeping the fallback from return as () would be inconsistent with such a change (although they are orthogonal), and might create conflicts down the road. This is fair.

The challenge with Err feedback (#40801) is that there are edge cases there too, like:

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

where the type of x is ultimately inferred to Result<T, !>.

I am reminded of a separate plan I had to try and see if we could determine (and perhaps warn? or error?) whether ! fallback ever "affected" live code -- this proved quite hard, though it seems like we could lint many cases in practice (e.g., that one, and probably @SimonSapin's case).

There was also discussion of whether we could remove this fallback altogether in the new edition. I suspect it will break a lot of macros, but it'd be maybe worth a try?

FYI, this feature caused a regression in 1.26: #49932

(Setting labels per https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

Should the checklist item about coercion into ! be pointing at https://github.com/rust-lang/rust/issues/50350 instead of referencing to this issue?

If I want this to be stabilized as soon as possible, where's the best place to direct my energy?

@remexre I think the best description of the problem that lead to reverting stabilization is at https://github.com/rust-lang/rust/issues/49593

So would a workable solution be to special-case Box as a subtype of
Box? Are there any objectsafe traits that can't be handled this way?

On Sun, Jul 8, 2018, 8:12 AM Ralf Jung notifications@github.com wrote:

@remexre https://github.com/remexre I think the best description of the
problem that lead to reverting stabilization is at #49593
https://github.com/rust-lang/rust/issues/49593

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Thanks,
Nathan

That discussion really should go into https://github.com/rust-lang/rust/issues/49593, but a key part of it is that Box::<T>::new requires T: Sized, but that new called is inferred to have T = Error (a trait DST) based on the return type.

Other than the inelegance of a special case, are there any issues to special-casing Box::new to be:

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

or

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

First of all, we should think about what is the size of ! should be. Mathemticans will propose something like Inf, but in reality it will be usize::MAX, so we are ensuring any attempts to allocate space for this type will fail or at least panic.

If ! is Sized then, there is nothing stopping us to compile Box::new(x as !), but this is basically another way to panic!, as no memory model can actually alocate usize::MAX bytes.

It doesn't seem obvious to me that ! should have infinite/usize::MAX size versus being a ZST?

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

currently is 0.

The reasonale was explained in the rendered text.

bool: two valid values => log(2)/log(2) = 1 bit
(): 1 valid values => log(1)/log(2) = 0 bit
!: 0 valid values => log(0)/log(2) = Inf bits

As someone not versed in the relevant theory, I see adhering to the log(x)/log(y) formula that far as chasing the elegance of a theoretical model to the detriment of practical use. (A.K.A. being too clever for one's own good)

Intuitively, it seems like ! should also be zero-sized because:

  • bool: needs space to distinguish between two valid values => log(2)/log(2) = 1 bit in addition to the type system
  • (): needs space to distinguish one valid value => no space needed beyond the type system
  • !: needs space to distinguish no valid values => no space needed beyond the type system

Well it's -infinity actually, which makes sense, as placing the ! as a field into a struct essentially turns the struct into ! as well (c + -inf = -inf). So since we are dealing with usize, 0 is the closest value we have to representing that. (I think the actual implementation in rustc even uses -inf).

Intuitively, it seems like ! should also be zero-sized

! doesn't need a size at all, because there'll never be any values created with that type. It's an irrelevant question.

0 valid values => log(0)/log(2) = Inf bits

log(0) is undefined; it's not infinite.

On the other hand, having a usize::MAX size is a tenichcal way to enforce uninhabitancy. Agree?

@CryZe @varkor

That also fits with the concept I was trying to reason out the validity of, where my intuition wants to see ! as being "ZST at a whole other level". (ie. ! is to the type system as () is to memory allocation.)

@CryZe
Your formula -Inf + c == -Inf makes sense, but when replace -Inf with 0usize it does not hold any more.

On the other hand, if the arithematic is "caped": any overflows calculate to usize::MAX then usize::MAX fits the formula perfectally.

The brain model: If you have an object of type !, to allocate a struct that contains it you need an even bigger struct than !, but the biggest struct size you can image is usize::MAX. So the space you need is still usize::MAX.

Why not just "clamp" the size of any type that contains a void type (!, or a user-defined enum Void {}) to size=0,alignment=0? This seems much more semantically sensible to me...

@remexre
Because this does not work. Example: sizeof(Result<usize,!>) == sizeof(usize).

Well, Result<usize, !> doesn't contain ! directly; sizeof(Result::Ok(usize)) == 8, sizeof(Result::Err(!)) == 0

No, sizeof(Result::Err(!)) == usize::MAX in my proposal, because Result::Err(!) :: Result<!,!>.

The rule should be: in enums, the size of ! is considered less than all other size values, but in structs, it is the maximum size can ever image.

Conclusion:

  • ! is not DST. DSTs are not Sized not because they don't have a size, but because the size information is hidden. But for ! we know everything about it.
  • ! should be sized and so Box::new(x as !) should be allowed at least to compile.
  • structs contains ! should be sized as if it is !, enums contains ! directally in a variant should be sized as if that variant does not exist.

Either we consider sizeof(!)==0 or sizeof(!)==usize::MAX, we have to introduce some special arithematic to allow the above:

sizeof(!)==0: in structs, 0 + n = 0:worried:, in enums, max(0,n) = n:blush:.
sizeof(!)==usize::MAX: in structs, usize::MAX + n =usize::MAX:neutral_face:, in enums, max(usize::MAX,n) = n:flushed:.

There is a benifit favours usize::MAX though: it technocially prevents any attempts, even unsafe ones, to construct such an object, as it is not possible in any real system.

There is a benifit favours usize::MAX though: it technocially prevents any attempts, even unsafe ones, to construct such an object, as it is not possible in any real system.

I mean, if unsafe shenanigans are allowed: *transmute::<Box<usize>, Box<!>>(Box::new(0)) gets me a ! regardless of its size. usize::MAX does make it more likely the compiler will error out if someone tries to do std::mem::zeroed<!>(), I suppose, but I would put the onus on the person who's writing that unsafe code, rather than the math weirdness above.

Note that ! having size _actually_ 0 -- not MAX nor -INF-saturated-to-0 -- is needed to handle partial initialization, as was found in https://github.com/rust-lang/rust/issues/49298#issuecomment-380844923 (and implemented in https://github.com/rust-lang/rust/pull/50622).

If you want to change how this works, please make a new issue or PR for that; this is not the place.

@remexre

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

Box is (mostly) a library type and its new method is defined in Rust syntax in src/liballoc/boxed.rs. Your description assumes a <: operator that does not exist in Rust. This is because subtyping in Rust only exists through lifetime parameters. If you’d like to read or watch more:

! can be coerced into any type, but this is weaker than being any type. (For example, Vec<&'static Foo> is also Vec<&'a Foo> for any 'a, but there’s no implicit conversion from Vec<!> to Vec<Foo>: http://play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

I think the idea is that you can do whatever you think is approprate in unsafe mode, but you have to face the UB if you don't do it wisely. If the official size of ! is usize::MAX, this should alert the user enough that this type should not be ever instanciated.

Also, I was thinking about the alignment. If uninhabited types have size usize::MAX, it is natural to set its alignment also usize::MAX. This effectially limits a valid pointer to be the null pointer. But by definition a null pointer is invalid, so we don't even have a valid point for this type, which is perfect for this case.

@ScottAbbey
I am focusing on the issue to make sure Box::new(x as !) don't have trouble so we can go on to stablize this. The proposed way is to make ! have a size. If ZST is the standard then it should be fine. I am not arguing any more. Just implement such that sizeof(!)==0 will do.

it is natural to set its alignment also usize::MAX

usize::MAX is not a power of two, so isn't a valid alignment. Also, LLVM doesn't support alignments of more than 1 << 29. And ! shouldn't have big alignment anyway, because let x: (!, i32); x.1 = 4; should only take 4 bytes of stack, not gigabytes-or-more.

Please make a new thread for this discussion, @earthengine, if you wish to continue it.

The problem that occurs here is that Box::new(!) is a Box<$0> where we expect a Box<Error>. With sufficient type annotations, we would require Box<$0> to be coerced to Box<Error>, which would later leave us with $0 defaulting to !.

However, $0 is a type variable, so coercion is not made and we then get $0 = Error.

One hacky option for fixing things would be finding that we have a constraint $0: Sized (following subtypes?) and inserting a coercion keyed on that, so that Box is still called at the type.

We already do something like this for detecting Fn in closures, so this won't be such a stretch. Still ugly.

That's it, if during coercion we encounter an obligation of the form $0: Unsize<Y> where Y is definitely unsized and $0 is definitely sized, don't treat that as an ambiguity but rather treat it as an "ok" and proceed with the unsized coercion.

@arielb1

So how this is different than coercion a Box::new(()) to Box<Debug>? std::error::Error is a trait like Debug, not a type like [u8]. std::io::Error on the other hand is a type, but it is Sized.

We never have trouble of the following:

use std::fmt::Debug;

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

fn main() {
}

@earthengine the difference between this issue and your code is:

| Written | Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | ------- |
| Performed | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

Essentially, because ! can coerce to anything, it is coerced to Debug before the call to Box::new. That's not an option with (), so there is no problem in the second case -- the coercion there happens after Box::new.

@RalfJung

My brain model is that DSTs cannot exist in the wild - they have to come from some more concret types. This is even true when we allow DSTs in the parameter position - if not in the return position. That said, until the real value being placed already behind a pointer, it should not be able to coace to a DST, even it is !.

For example the following should not compile:

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

Simply because you cannot replace ! with any existing Rust expression to make it compile.

Edit

That's not an option with ()

So can anyone explain why? If () as dyn Debug does not compile, ! as dyn Debug shall not and vice versa. &() as &Debug compiles, so do &! as &Debug, no problems. If () as dyn Debug may some day compiles, the problem we have today will be repeated for (), so the DST RFC implementors will have to due with it, and so it will solve the same problem we have for !.

! coerces to anything because it is impossible to exist. "Ex falso quodlibet". So all of your examples should compile -- there is no good theoretical reason to make a special exception for unsized types.

This does not even violate "DTS cannot exist" because ! cannot exist, either. :)

() as dyn Debug does not compile

I don't know what the plans are for unsized rvalues, but I guess they could make this compile?
The point is, doing this for ! does not require us to implement unsized rvalues because this can only happen in dead code.

However, maybe you are pointing at a possible solution here (and maybe that's what @arielb1 also suggested above): Is it possible to restrict the ! coercion to only apply if the target type is (known to be) sized? There's no good theoretical reason to do this but a practical one. :D Namely, that it might help to solve this problem.

When I said "DTS cannot exist" I mean in syntactical. It is not possible to have a local variable that is typed is str, for example.

On the other hand, ! cannot exist is semantical. You can write

let v = exit(0);

to have v syntactically typed ! in the context, but of cause the binding will not even run and so it does not exit in the real world.

So here is the reasonale: we allow ! coerce to any type, only when you can write an expression that have the same type. If a type cannot even exist syntactically, it should not be allowed.

This also apply to unsized rvalues, so before we have unsized rvalues, coerce ! to unsized types is not allowed, until we know how to handle unsized rvalues. And only at that point we can start think about this problem (one obvious solution seems to be allow Box::new receive unsized values).

It is not possible to have a local variable that is typed is str, for example.

That's just a limitation of the current implementation: https://github.com/rust-lang/rust/issues/48055

@RalfJung

That's not the problem here.

Again, suppose we're at the situation where Box::new(!: $0): Box<dyn fmt::Debug> where $0 is a type variable.

Then the compiler can fairly easily deduce that $0: Sized (because $0 is equal to the type parameter of Box::new, which has a T: Sized bound).

The problem comes where the compiler needs to figure out which sort of coercion to use to transform the Box<$0> to a Box<dyn fmt::Debug>. From a "local" POV, there are 2 solutions:

  1. Have a CoerceUnsized coercion, requiring Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>>. This is a valid program for $0 = ! and defaulting will make it compile to that.
  2. Have an identity coercion, with $0 = dyn fmt::Debug. This is inconsistent with the $0: Sized requirement.

The compiler doesn't want to have too much stuff open when considering ambiguities, as that can cause both performance issues and hard-to-debug problems, so it picks out which coercion to use in a fairly stupid manner (in particular, we don't have T: CoerceUnsized<T>, so if the compiler picks option 1 it can get "stuck" pretty easily), and it ends up picking option 2, which fails. I had an idea to make it a little smarter and pick option 1.

So thinking on it from this POV, the right idea might be to trigger an unsized coercion if

  1. The unsized coercion is self-consistent (that would be the current rules, except that ambiguity is OK).
  2. not triggering the unsized coercion is inconsistent. We have to see that this can be calculated without ruining performance on larger functions.

Have an identity coercion, with $0 = dyn fmt::Debug. This is inconsistent with the $0: Sized requirement.

I can hardly see that why should us allow let v: str=! but not let v: str;. If you cannot even declare a variable with a specific type, why can you bind a (maybe impossible) value to it?

When unsized rvalue is not shipped, the consistent way is not to allow any coercion to unsized types, as this will create (maybe temporary) unsized rvalues, even when this happens only in imagination.

So my conclusion is that the Box::new(!) as Box<Error> problem is a blocking issue for unsized rvalues, not for the ! type. The ! coercion rules should be:

You can write let v: T, if and only if you can write let v: T = !.

Thea actual meaning will then evaluate with the language. Especially, after we have unsized rvalues, we have Box::new(! as dyn Debug) but before that, I would say no.

So what we can do to move forward?

Add a feature gate on the code that triggers the unsized coercion, if unsized rvalue is on, try this coercion, otherwise, skip it. If this is the only available coercion, the error message should be "the trait bound str: std::marker::Sized is not satisfied", like in similar cases. So

We have to see that this can be calculated without ruining performance on larger functions.

is addressed: just a simple feature check.

Meanwhile, add a new issue for unsized rvalues, and have the link added to the tracking issue, so to make sure this issue is addressed properly.

Insteresting.

This

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

compiles, but

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

do not. This is not even related to the ! type. Shall I create an issue for this?

I don’t know if that last one really is a bug. The second example does not compile because, without unsized rvalue support, the compiler wants to know statically how much stack space to allocate for the local variable v but can’t because it’s a DST.

In the first example, the _ pattern is special in that it not only matches anything (like a local variable binding), but it does not even create a variable at all. So that code is the same as fn main() { *""; } without let. Dereferencing a reference (even DST) and then doing nothing with the result is not useful, but it seems to be valid and I’m not convinced it should be invalid.

Right. But it is really confusing, especially the following

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

does not compile either. With your theory about _ this should be the same except that we call the unused thing _v rather than just _.

From what I remember, the only difference between _v and v is that the leading underscore suppresses warnings about unused values.

On the other hand, _ specifically means "discard it" and is handled specially in order to enable it to appear in more than one place in a pattern (eg. tuple unpack, arguments list, etc.) without causing an error about a name collision.

On the other hand, _ specifically means "discard it" and is handled specially in order to enable it to appear in more than one place in a pattern (eg. tuple unpack, arguments list, etc.) without causing an error about a name collision.

Correct. On top of what you said, let _ = foo() leads to drop being called immediately, whereas _v only gets dropped when it goes out of scope (like v would).

Indeed, this is what I meant by “_ is special”.

So now it looks like denying all unsized rvalues (unless feature "unsized rvalues" turned on) in the wild would be a breaking change, although the effect would be little I think.

I then still suggest to have a feature gate to disallow coersion from ! to unsized rvalues. This would deny code like let _:str = return;, but I don't think any one will use it in code.

As long as the ! type is unstable we can make breaking changes to where it can or cannot be coerced.

The question is, if we stabilize it without coercion to DSTs in order to fix https://github.com/rust-lang/rust/issues/49593, will we want to restore such coercion later when we add unsized rvalues and would we be able to do so without breaking https://github.com/rust-lang/rust/issues/49593 again?

My previous proposal includes this. #49593 should be a blocking issue for unsized rvalues.

My personal suggestion for the final solution, is to let Box::new (may also include some other important methods) accepts unsized parameters, and allow ambiguities in similar situations.

FWIW, _ is nothing like a special variable name. It's an entirely separate pattern syntax which does nothing to the place being matched. Pattern-matching works on places, not values.
The closest it comes to a variable binding is ref _x, but even then you probably need NLL to avoid conflicting borrowing resulting from it. This works btw:

// 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

You didn't get my point. My assumsion is that in Rust there is currently no unsized rvalues can appear in code at all. However it turns out that they appear in let _:str = *"". Although such a value can live for no time, in syntatical level it does live. Something like a ghost...

Your examples instead, is technically fully legal and it is not the point. str is unsized, but &str is sized.

However it turns out that they appear in let _:str = *"". Although such a value can live for no time, in syntatical level it does live. Something like a ghost...

There is a similar "ghost" in an expression like &*foo. First you dereference foo and then take the address of the result without moving it. So it’s not the same as & { *foo }, for example.

@earthengine @SimonSapin No unsized rvalues ("value expression") exist there. Only lvalues ("place expressions") do. *"foo" is a place, and pattern-matching it doesn't require a value, only a place.

The names "lvalue" and "rvalue" are relics and somewhat misleading in C too, but even worse in Rust, because let's RHS is an "lvalue" (place expression), despite the name.
(Assignment expressions are the only place where the C names and rules sort of make sense in Rust)

In your examples, let x = *"foo";, the value is required to bind it to x.
Similarly, &*"foo" creates a place expression then borrows it, without ever needing to create a an unsized value, while {*"foo"} is always a value expression and thus doesn't allow unsized types.

In https://github.com/rust-lang/rust/pull/52420 I hit that

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

no longer works. Is that intentional?

AFAIK yes -- the infallible pattern story is complicated and has a separate feature gate from ! itself. Also see https://github.com/rust-lang/rfcs/pull/1872 and https://github.com/rust-lang/rust/issues/48950.

@Ericson2314 specifically the feature you would need to add to liballoc is exhaustive_patterns.

Thanks!!

An interesting conversation on internals:

If you're going to do that, why not treat ! itself as an inference variable?

Just make a wrapper around ! with a PhandomData to disambiguate the item type.

https://github.com/rust-lang/rust/issues/49593 has now been fixed. This was the reason for the previous revert of stabilization. Previous stabilization report is here. Let's try this again!

@rfcbot fcp merge

I think rfcbot might support more than one FCP in the same issue. Let’s open a new issue for this round of stabilization?

https://github.com/rust-lang/rust/pull/50121 not only reverted stabilization but also the fallback semantics. Is this something we want to revisit?

The remaining unchecked checkbox in the issue description is:

What traits should we implement for !? The initial PR #35162 includes Ord and a few others. This is probably more of a T-libs issue, so I'm adding that tag to the issue.

We can add impls later, can’t we? Or is this a blocker?

@SimonSapin Opened https://github.com/rust-lang/rust/issues/57012. I'd expect that the fallback to ! would be enabled again as part of this change, yes (though let's discuss that on the stabilization issue).

Apparently there’s bug/hole in having the never type behind a feature gate, and it’s possible to refer to it on Stable: https://github.com/rust-lang/rust/issues/33417#issuecomment-467053097

Edit: filed https://github.com/rust-lang/rust/issues/58733.

Adding an use of the type in the code example linked above:

trait MyTrait {
    type Output;
}

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

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

fn main() {
    let _a: Void;
}

This compiles in Rust 1.12.0 which I think is the first with https://github.com/rust-lang/rust/pull/35162. In 1.11.0, it errors with:

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

What is the state of this one?

As far as I know the status has not significantly changed since my summary in https://github.com/rust-lang/rust/issues/57012#issuecomment-467889706.

What is the state of this one?

@hosunrise: The is currently blocked on https://github.com/rust-lang/rust/issues/67225

I may be way off here but the specific issues that the lint discussion (https://github.com/rust-lang/rust/issues/66173) mentions both seem solvable if every enum has a ! branch in the type system?

I will note that does not apply to the issue mentioned in the OP of https://github.com/rust-lang/rust/issues/67225, which would still be an issue.

I may be way off here but the specific issues that the lint discussion (#66173) mentions both seem solvable if every enum has a ! branch in the type system?

Actually, every enum can be threated as there was infinite number of variants that can never occur, and thus they all aren't worth of mentioning.

Was this page helpful?
0 / 5 - 0 ratings