Skip to content

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

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

Open
2 tasks
nikomatsakis opened this issue Jul 29, 2016 · 376 comments
Open
2 tasks

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

nikomatsakis opened this issue Jul 29, 2016 · 376 comments
Assignees
Labels
A-type-system Area: Type system B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC F-never_type `#![feature(never_type)]` Libs-Tracked Libs issues that are tracked on the team's project board. S-tracking-needs-to-bake Status: The implementation is "complete" but it needs time to bake. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jul 29, 2016

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

About tracking issues

Tracking issues are used to record the overall progress of implementation. They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions. A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature. Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Pending issues to resolve

Interesting events and links

Latest Status

#35121 (comment):

The plan, as FCPed here, is to:

  1. Change fallback to ! in Rust 2024 (details of this elided as this work is done).
  2. Stabilize Rust 2024.
  3. At a later date when 2024 edition adoption is widespread:
    • Make the breaking change to fall back to ! always everywhere.
    • Change Infallible = !.
    • Stabilize ! (!).

Rust 2024 is expected to be released in Q1 2025. We're not sure how long step 3 will take after that.

See the draft edition guide chapter for further background here:

https://doc.rust-lang.org/nightly/edition-guide/rust-2024/never-type-fallback.html

@nikomatsakis nikomatsakis added B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. B-unstable Blocker: Implemented in the nightly compiler and unstable. labels Jul 29, 2016
@canndrew
Copy link
Contributor

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

@nikomatsakis nikomatsakis added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Aug 10, 2016
@tomaka
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
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
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
Copy link
Contributor

@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
Copy link
Contributor

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

@canndrew
Copy link
Contributor

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

@arielb1
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
Copy link
Contributor Author

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
Copy link
Contributor

#36011
#36038
#35940

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

@glaebhoerl
Copy link
Contributor

@canndrew those look a bit similar to #12609

@canndrew
Copy link
Contributor

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

@lambda-fairy
Copy link
Contributor

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

@orium
Copy link
Member

orium commented May 17, 2024

Thanks for the update.

it can make code which compiled fine, into code that has UB.

That sounds quite scary. Can you elaborate on it? (From the lint PR it seems it only affects unsafe code?)

@WaffleLapkin
Copy link
Member

@orium inferring a different type of a call to an unsafe function can cause UB, see example. This is indeed quite scary and why this issue was blocked for so long.

@bp-galopin
Copy link

bp-galopin commented Jun 11, 2024

Hi, stumble into a case that the never type appears to be the solution for me but the never documentation does not mention that use case, so would it be a proper case for it ?

pub trait FuncTrait: Fn() {}
fn foo::<Func : FuncTrait>( func: Option<Func>) {}
// and at use site:
foo( None ); // <-- cannot satisfy `_: FuncTrait`  
foo::<!>( None ); // I think would work, but I am running stable, and never doc mention it is only for return types, so ?

Unless there is a different way to have rust figured the type is not be constructed and does not need to be known ?

@Ciel-MC
Copy link
Contributor

Ciel-MC commented Jun 11, 2024

Hi, stumble into a case that the never type appears to be the solution for me but the never documentation does not mention that use case, so would it be a proper case for it ?

pub trait FuncTrait: Fn() {}
fn foo::<Func : FuncTrait>( func: Option<Func>) {}
// and at use site:
foo( None ); // <-- cannot satisfy `_: FuncTrait`  
foo::<!>( None ); // I think would work, but I am running stable, and never doc mention it is only for return types, so ?

Unless there is a different way to have rust figured the type is not be constructed and does not need to be known ?

I think it would work? You can use the never say never crate to hack the real ! into existence on stable, just to verify if it works, just as a heads up, it’s not on stable for a reason.

@2xsaiko
Copy link

2xsaiko commented Jun 11, 2024

@bp-galopin You can use the enum std::convert::Infallible for the same purpose right now. Keep in mind that you have to implement FuncTrait for Infallible first, but IIRC that would be the case for ! as well as of right now.

@HotoRas
Copy link

HotoRas commented Jul 20, 2024

Could I get some updates from this?

@traviscross
Copy link
Contributor

traviscross commented Jul 20, 2024

The plan, as FCPed here, is to:

  1. Change fallback to ! in Rust 2024 (details of this elided as this work is done).
  2. Stabilize Rust 2024.
  3. At a later date when 2024 edition adoption is widespread:
    • Make the breaking change to fall back to ! always everywhere.
    • Change Infallible = !.
    • Stabilize ! (!).

Rust 2024 is expected to be released in Q1 2025. We're not sure how long step 3 will take after that.

See the draft edition guide chapter for further background here:

https://doc.rust-lang.org/nightly/edition-guide/rust-2024/never-type-fallback.html

@HotoRas
Copy link

HotoRas commented Jul 20, 2024

Rust 2024 is expected to stabilize in Q1 2025. We're not sure how long step 3 will take after that.

Checked, faster than expected

@Kixunil
Copy link
Contributor

Kixunil commented Jul 25, 2024

I have some questions regarding 3).

  • Make the breaking change to fall back to ! always everywhere.

This is a breaking change isn't it? It looks like it could also produce unsoundness in unsafe code. Doesn't it break Rust's stability guarantees?

  • Change Infallible = !.

Why does it have to wait until widespread 2024 adoption?

  • Stabilize ! (!).

How can the fallback change without stabilizing in the first place? And why can't it be stabilized with edition 2024 release?

@WaffleLapkin
Copy link
Member

@Kixunil

This is a breaking change isn't it? It looks like it could also produce unsoundness in unsafe code. Doesn't it break Rust's stability guarantees?

Yes, it is a breaking change. Yes it can produce unsoundness in unsafe code, however note that we have a lint such unsoundness which is also reported in deps. No, it does not break Rust's stability guarantees, technically Rust permits inference changes (still, this is not something to take lightly, we are trying to take all imaginable precautions to make sure it's as light change as it can be).

How can the fallback change without stabilizing in the first place?

We can just do it. Not sure what to add here.

And why can't it be stabilized with edition 2024 release?

Language team fears the sudden change will break too many things, as planned, we are providing a grace period for everyone affected to patch their code, while making e2024 have the semantics we want from the start.

Why does it have to wait until widespread 2024 adoption?

Infallible = ! and stabilization of ! must happen in the same release (otherwise it would de-stabilize Infallible or make it observable that they are different types).

If you want to learn more, please read recording of T-lang design meeting about the never type, and then a second discussion of some details.

@Kixunil
Copy link
Contributor

Kixunil commented Jul 25, 2024

@WaffleLapkin Thanks for explaining! In particular, the design meeting recording convinced me this is likely the least bad option. I'm just not sure if the lint is 100% reliable since if it isn't it can be a huge problem.

@Seidko

This comment was marked as off-topic.

@compiler-errors
Copy link
Member

@Seidko, and others who are interested in reporting bugs:

Please open a separate bug if you think there is a bug with the never type implementation. This tracking issue does not say it since it likely precedes its inclusion in other tracking issues, but

Tracking issues are used to record the overall progress of implementation. They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions. A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature. Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

@kornelski

This comment has been minimized.

@coolCucumber-cat

This comment has been minimized.

@RalfJung
Copy link
Member

RalfJung commented Apr 10, 2025

This is an issue for tracking the remaining work involved in stabilizing this type, not for "+1" comments. Those comments do not contribute anything to the progress of this feature. So please keep them out of this issue.

@kornelski you know entirely well that the type has not been stabilized, and the code you describe works by accident.

@Jules-Bertholet
Copy link
Contributor

type Never = <u8 as TryFrom<u8>>::Error;

This is Infallible, not the never type.

@kornelski
Copy link
Contributor

kornelski commented Apr 10, 2025

you know entirely well that the type has not been stabilized, and the code you describe works by accident.

It may have been unintentional, but the ! type is already available on stable. Users can depend on it by using only features that are already guaranteed to be stable. Type aliases and associated types are pretty standard ways of using unnameable types. There's no unstable opt-in required. No feature flags. No warnings.

Changes to the fallback in the 2024 edition changed the situation. It's no longer just a trick for accessing an unstable (unnameable) type, but a necessity for dealing with the type that appears in stable code via type inference in the stable compiler.

Currently Bevy needs to implement traits on the ! type, and will probably exploit this accident to use the ! type on stable.

The ship has sailed for the type stabilization. Only the literal syntax is not on stable.

@WaffleLapkin
Copy link
Member

WaffleLapkin commented Apr 10, 2025

@kornelski the hack was known for many years and it doesn't make the type stable. If you are using the hack, you are knowingly opting out of stability guarantees. There is nothing new about this.

Changes to the fallback in the 2024 edition changed the situation. It's no longer just a trick for accessing an unstable type, but a necessity for dealing with the type that appears in stable code via type inference in the stable compiler.

Currently Bevy needs to implement traits on the ! type, and will probably exploit this accident to use the ! type on stable.

It's not a necessity. You can always return the old behavior by specifying the () type explicitly. If bevy wants to use this hack to make their library nicer to use, it's their choice, but they don't have to do it.

@chorman0773
Copy link
Contributor

As a procedural question, this issue has finished-final-comment-period, but no disposition? Looks like it's left-over from a previous stabilization, that might be a good idea to clear.

Is there an up-to-date list of blockers? The issue description itself seems to have all of its linked issues closed.

@fmease fmease added S-tracking-needs-to-bake Status: The implementation is "complete" but it needs time to bake. and removed finished-final-comment-period The final comment period is finished for this PR / Issue. S-tracking-design-concerns Status: There are blocking design concerns. labels Apr 10, 2025
@slanterns
Copy link
Contributor

slanterns commented Apr 10, 2025

Is there an up-to-date list of blockers? The issue description itself seems to have all of its linked issues closed.

I believe the is no explicit "blocker", but we need to wait: #35121 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-type-system Area: Type system B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC F-never_type `#![feature(never_type)]` Libs-Tracked Libs issues that are tracked on the team's project board. S-tracking-needs-to-bake Status: The implementation is "complete" but it needs time to bake. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
Status: Idea
Development

No branches or pull requests