Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for promoting `!` to a type (RFC 1216) #35121

Open
nikomatsakis opened this Issue Jul 29, 2016 · 253 comments

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jul 29, 2016

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

Pending issues to resolve:

  • 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.
  • Remove coercion into type ! #35121
  • How to implement warnings for people relying on (): Trait fallback where that behavior might change in the future?
  • Desired semantics for ! in coercion (#40800)
  • Which type variables should fallback to ! (#40801)
  • 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
  • Resolve ICE with *const ! - #43061
  • #47563 -- [!; 0] not uninhabited
  • #49593 - Box::new(!): Box<Error> does not compile.

Related:

  • #46164: we could do a better job linting questionable fallback behavior
  • #46325 -- future compat warning for coercion to !

Interesting events:

@canndrew

This comment has been minimized.

Copy link
Contributor

canndrew commented Jul 30, 2016

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?

@canndrew

This comment has been minimized.

Copy link
Contributor

canndrew commented Aug 1, 2016

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).

dns2utf8 added a commit to dns2utf8/rust that referenced this issue Aug 4, 2016

dns2utf8 added a commit to dns2utf8/rust that referenced this issue Aug 4, 2016

@nikomatsakis nikomatsakis added the T-libs label Aug 10, 2016

@tomaka

This comment has been minimized.

Copy link
Contributor

tomaka commented Aug 11, 2016

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.

@suhr

This comment has been minimized.

Copy link

suhr commented Aug 11, 2016

@tomaka There's RFC about it: rust-lang/rfcs#1637

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

@tomaka

This comment has been minimized.

Copy link
Contributor

tomaka commented Aug 11, 2016

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

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

@canndrew

This comment has been minimized.

Copy link
Contributor

canndrew commented Aug 11, 2016

@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.

@canndrew

This comment has been minimized.

Copy link
Contributor

canndrew commented Aug 11, 2016

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

dns2utf8 added a commit to dns2utf8/rust that referenced this issue Aug 15, 2016

@canndrew

This comment has been minimized.

Copy link
Contributor

canndrew commented Aug 19, 2016

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.

@tomaka

This comment has been minimized.

Copy link
Contributor

tomaka commented Aug 19, 2016

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.

dns2utf8 added a commit to dns2utf8/rust that referenced this issue Aug 20, 2016

@arielb1

This comment has been minimized.

Copy link
Contributor

arielb1 commented Aug 20, 2016

@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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

nikomatsakis commented Aug 24, 2016

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

This comment has been minimized.

Copy link
Contributor

canndrew commented Aug 27, 2016

#36011
#36038
#35940

Are some bugs to add to the list of pending issues.

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Aug 27, 2016

@canndrew those look a bit similar to #12609

@canndrew

This comment has been minimized.

Copy link
Contributor

canndrew commented Aug 27, 2016

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.

@tikue

This comment has been minimized.

Copy link
Contributor

tikue commented Aug 29, 2016

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

This comment has been minimized.

Copy link
Contributor

lfairy commented Aug 29, 2016

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

@earthengine

This comment has been minimized.

Copy link

earthengine commented Jul 11, 2018

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.

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Jul 11, 2018

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 #49593, will we want to restore such coercion later when we add unsized rvalues and would we be able to do so without breaking #49593 again?

@earthengine

This comment has been minimized.

Copy link

earthengine commented Jul 11, 2018

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.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jul 13, 2018

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";
@earthengine

This comment has been minimized.

Copy link

earthengine commented Jul 13, 2018

@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.

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Jul 13, 2018

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.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jul 13, 2018

@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.

@Ericson2314

This comment has been minimized.

Copy link
Contributor

Ericson2314 commented Jul 16, 2018

In #52420 I hit that

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

no longer works. Is that intentional?

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented Jul 16, 2018

AFAIK yes -- the infallible pattern story is complicated and has a separate feature gate from ! itself. Also see rust-lang/rfcs#1872 and #48950.

@Nemo157

This comment has been minimized.

Copy link
Contributor

Nemo157 commented Jul 16, 2018

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

@Ericson2314

This comment has been minimized.

Copy link
Contributor

Ericson2314 commented Jul 16, 2018

Thanks!!

@ExpHP

This comment has been minimized.

Copy link
Contributor

ExpHP commented Dec 16, 2018

An interesting conversation on internals:

@taralx

This comment has been minimized.

Copy link
Contributor

taralx commented Dec 17, 2018

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

@Ericson2314

This comment has been minimized.

Copy link
Contributor

Ericson2314 commented Dec 17, 2018

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

@cramertj

This comment has been minimized.

Copy link
Member

cramertj commented Dec 20, 2018

#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

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Dec 20, 2018

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

#50121 not only reverted stabilization but also the fallback semantics. Is this something we want to revisit?

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Dec 20, 2018

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?

@cramertj

This comment has been minimized.

Copy link
Member

cramertj commented Dec 20, 2018

@SimonSapin Opened #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).

@rust-lang rust-lang deleted a comment from rfcbot Dec 28, 2018

@rust-lang rust-lang deleted a comment from rfcbot Dec 28, 2018

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Feb 13, 2019

Cross-referencing: #58184

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Feb 25, 2019

Apparently there’s bug/hole in having the never type behind a feature gate, and it’s possible to refer to it on Stable: #33417 (comment)

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Feb 25, 2019

Edit: filed #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 #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

vsrinivas pushed a commit to vsrinivas/fuchsia that referenced this issue Mar 13, 2019

[rust] Use void crate, not hand-rolled Never types
- Replace a number of bespoke 'enum Never {}' implementations with
  the void::Void type
- Once rust-lang/rust#35121 is stabilized,
  we can replace void::Void with the never type (!)

Change-Id: I6557146636132ae3395393b8cc8f62b3214f3177
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.