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

Pre-RFC: Add an Empty type to libcore. Use it to replace the fn() -> ! syntax. #1001

Closed
canndrew opened this issue Mar 22, 2015 · 12 comments
Closed

Comments

@canndrew
Copy link
Contributor

  • Start Date: (fill me in with today's date, YYYY-MM-DD)
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

Summary

Add an Empty type to libcore. Use it to replace the fn() -> ! sytax.

Motivation

See some previous discussion here: http://internals.rust-lang.org/t/should-be-a-type/1723 here: rust-lang/rust#20172 and here: rust-lang/rust#18414

Edit: @reem has already implemented a crate for this. See: https://github.com/reem/rust-void

A standard Empty type (ie. an enum with no variants) would be a useful addition to the standard library. One use case is if you need to implement a trait method that returns Result<_, Err> where Err is an associated type but you know that your implementation cannot fail. Currently, library authors can easily define their own empty types, but this will result in multiple libraries defining the same thing.

Making diverging functions ordinary functions also allows them to be used in generic code. For example, it would allow functions that diverge to be passed as arguments to higher-order functions.

Removing ! and replacing it with a plain ol' type will make Rust slightly simpler.

Detailed design

Add this to libcore:

[lang="empty"]
enum Empty {}

impl Empty {
    fn elim<T>(self) -> T {
        match self {}
    }
}

Add an empty lang item so that intrinsics like abort can return it. Make all methods that are currently marked as diverging (eg. panic, abort) return Empty instead. Make the panic! macro expand to begin_unwind(...).elim() instead of just begin_unwind(...) to avoid users having to add the .elim() themselves.

Add a convenience method to Result<T, Empty>:

impl<T> Result<T, Empty> {
    fn to_inner(self) -> T {
        match self {
            Ok(x)  => x,
            Err(e) => e.elim(),
        }
    }
}

Drawbacks

This change will require typing .elim() where previously someone wouldn't have had to. But only when calling a diverging function from a non-diverging function without using one of the panic! family macros. In the future, Rust's type checker could be improved to allow any type which is isomorphic to Empty to unify with any other type.

It might make things like reachability checking more difficult. But it would be nice anyway if the compiler could detect unreachable code after code that produces an empty value.

! used to be treated as a type though, but this was removed due to maintenance difficulties.

Alternatives

Just add an Empty type to libcore but keep the seperate divergence syntax aswell.

Name it Never as in "this value can never exist" or "this function can never return". Name it Void. However this is likely to confuse programmers from a C-family language background as Void is not (). Name it Bottom. Name it something else.

Unresolved questions

Is there time to make this change before 1.0?

@llogiq
Copy link
Contributor

llogiq commented Mar 22, 2015

One other alternative is to keep the ! as syntactic sugar for the not-type.

@glaebhoerl
Copy link
Contributor

Make the panic! macro expand to begin_unwind(...).elim() instead of just begin_unwind(...) to avoid users having to add the .elim() themselves.

The advantage of the current built-in support for ! is that it unifies with any other type automatically, without the need for explicit elim() calls, and this works for anything that returns !, not just panic! or other macros. (I.e. this is what would belong in the Drawbacks section.) Until/unless Rust is capable of dealing with a type like for<T> T directly, I would be in favor of keeping this. It's nice.

If we do want to add a library type instead of or in addition to it, I think it should be called Never.

@kennytm
Copy link
Member

kennytm commented Mar 22, 2015

The bottom type was removed in rust-lang/rust#14973, because of maintenance difficulty?

I don't think it's very important for 1.0 though, requiring the user to provide a must-be-never-ending closure Fn() -> ! seems unusual.

@canndrew
Copy link
Contributor Author

The advantage of the current built-in support for ! is that it unifies with any other type automatically, without the need for explicit elim() calls

How often do you call a diverging function from a non-diverging function without using panic! or assert! et. al? Having a whole extra construct in the language which breaks the ability to use things like Fn() -> T traits seems like large price to pay to avoid having to type .elim() occasionally.

and this works for anything that returns !, not just panic! or other macros

And similarly .elim() will work on anything that returns Empty.

@canndrew
Copy link
Contributor Author

If we do want to add a library type instead of or in addition to it, I think it should be called Never.

I quite like this. Then the name Empty could be reserved for a trait of types that are empty.

@canndrew
Copy link
Contributor Author

I don't think it's very important for 1.0 though

It's only important because it would be a backwards-incompatible change.

@reem
Copy link

reem commented Mar 23, 2015

From an optimization perspective, the implementation of elim can call intrinsics::unreachable to get LLVM in on the unreachability of calls to elim or any code following a call to elim.

Bikeshed: elim should be called unreachable, since that's what it means.

Note that there exists (disclaimer: I wrote it) a crate with a similar type and helpers called void, whose code is here: https://github.com/reem/rust-void

@reem
Copy link

reem commented Mar 23, 2015

Also, a similar empty type used to exist in std::any.

@llogiq
Copy link
Contributor

llogiq commented Mar 23, 2015

@canndrew: Why would it not be backwards-compatible (provided we choose to keep ! as syntactic sugar)? Can you provide an example of a program that would break?

@canndrew
Copy link
Contributor Author

@llogiq I meant if the ! syntax was removed. Too late for that now though.

@llogiq
Copy link
Contributor

llogiq commented May 20, 2015

Too late for that now though.

Agreed. Well, I think the ! syntax is at least obviously confusing. Whereas any of the suggested names would have been confusing to some, but not all. And reem's implementation seems to handle the cases where the usefulness outweighs the confusion. 😄

@Stebalien
Copy link
Contributor

Triage: Superseded by #1216

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants