From 58baeaa1dcc97216215c5f2bcda7627a88665455 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Sun, 19 Jul 2015 14:21:09 +0800 Subject: [PATCH 1/6] bang_type initial commit --- text/0000-bang-type.md | 401 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 text/0000-bang-type.md diff --git a/text/0000-bang-type.md b/text/0000-bang-type.md new file mode 100644 index 00000000000..72bf837f59c --- /dev/null +++ b/text/0000-bang-type.md @@ -0,0 +1,401 @@ +- Feature Name: bang_type +- Start Date: 2015-07-19 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Promote `!` to be a full-fledged type equivalent to an `enum` with no variants. + +# Motivation + +To understand the motivation for this it's necessary to understand the concept +of empty types. An empty type is a type with no inhabitants, ie. a type for +which there is nothing of that type. For example consider the type `enum Never +{}`. This type has no constructors and therefore can never be instantiated. It is empty, in the sense that there are no values of type `Never`. Note +that `Never` is not equivalent to `()` or `struct Foo {}` each of which have +exactly one inhabitant. Empty types have some interesting properties that may +be unfamiliar to programmers who have not encountered them before. + + * They never exist at runtime. + Because there is no way to create one. + + * They have no logical machine-level representation. + One way to think about this is to consider the number of bits required to + store a value of a given type. A value of type `bool` can be in two + possible states (`true` and `false`). Therefore to specify which state a + `bool` is in we need `log2(2) ==> 1` bit of information. A value of type + `()` can only be in one possible state (`()`). Therefore to specify which + state a `()` is in we need `log2(1) ==> 0` bits of information. A value of + type `Never` has no possible states it can be in. Therefore to ask which of + these states it is in is a meaningless question and we have `log2(0) ==> + undefined` (or `-∞`). Having no representation is not problematic as safe + code never has reason nor ability to handle data of an empty type (as such + data can never exist). In practice, Rust currently treats empty types as + having size 0. + + * Code that handles them never executes. + Because there is no value that it could execute with. Therefore, having a + `Never` in scope is a static guarantee that a piece of code will never be + run. + + * They represent the return type of functions that don't return. + For a function that never returns, such as `exit`, the set of all values it + may return is the empty set. That is to say, the type of all values it may + return is the type of no inhabitants, ie. `Never` or anything equivalent to + it. + + * They can be converted to any other type. + To specify a function `A -> B` we need to specify a return value in `B` for + every possible argument in `A`. For example, an expression that converts + `bool -> T` needs to specify a return value for both possible arguments + `true` and `false`: + + ```rust + let foo: &'static str = match x { + true => "some_value", + false => "some_other_value", + }; + ``` + + Likewise, an expression to convert `() -> T` needs to specify one value, + the value corresponding to `()`: + + ```rust + let foo: &'static str = match x { + () => "some_value", + }; + ``` + + And following this pattern, to convert `Never -> T` we need to specify a + `T` for every possible `Never`. Of which there are none: + + ```rust + let foo: &'static str = match x { + }; + ``` + + Reading this, it may be tempting to ask the question "what is the value of + `foo` then?". Remember that this depends on the value of `x`. As there are + no possible values of `x` it's a meaningless question and besides, the + fact that `x` has type `Never` gives us a static guarantee that the match + block will never be executed. + +Here's some example code that uses `Never`. This is legal rust code that you +can run today. + +```rust +use std::process::exit; + +// Our empty type +enum Never {} + +// A diverging function with an ordinary return type +fn wrap_exit() -> Never { + exit(0); +} + +// we can use a `Never` value to diverge without using unsafe code or calling +// any diverging intrinsics +fn diverge_from_never(n: Never) -> ! { + match n { + } +} + +fn main() { + let x: Never = wrap_exit(); + // `x` is in scope, everything below here is dead code. + + let y: String = match x { + // no match cases as `Never` has no variants + }; + + // we can still use `y` though + println!("Our string is: {}", y); + + // we can use `x` to diverge + diverge_from_never(x) +} +``` + +This RFC proposes that we allow `!` to be used directly, as a type, rather than +using `Never` (or equivalent) in it's place. Under this RFC, the above code +could more simply be written. + +```rust +use std::process::exit; + +fn main() { + let x: ! = exit(0); + // `x` is in scope, everything below here is dead code. + + let y: String = match x { + // no match cases as `Never` has no variants + }; + + // we can still use `y` though + println!("Our string is: {}", y); + + // we can use `x` to diverge + x +} +``` + +So why do this? AFAICS there are 3 main reasons + + * **It removes one superfluous concept from the language and allows diverging + functions to be used in generic code.** + + Currently, Rust's functions can be divided into two kinds: those that + return a regular type and those that use the `-> !` syntax to mark + themselves as diverging. This division is unnecessary and means that + functions of the latter kind don't play well with generic code. + + For example: you want to use a diverging function where something expects a + `Fn() -> T` + + ```rust + fn foo() -> !; + fn call_a_fn T>(f: F) -> T; + + call_a_fn(foo) // ERROR! + ``` + + Or maybe you want to use a diverging function to implement a trait method + that returns an associated type: + + ```rust + trait Zog { + type Output + fn zog() -> Output; + }; + + impl Zog for T { + type Output = !; // ERROR! + fn zog() -> ! { panic!("aaah!") }; // ERROR! + } + ``` + + The workaround in these cases is to define a type like `Never` and use it + in place of `!`. You can then define functions `wrap_foo` and `unwrap_zog` + similar to the functions `wrap_exit` and `diverge_from_never` defined + earlier. It would be nice if this workaround wasn't necessary. + + * **It creates a standard empty type for use throughout rust code.** + + Empty types are useful for more than just marking functions as diverging. + When used in an enum variant they prevent the variant from ever being + instantiated. One major use case for this is if a method needs to return a + `Result` to satisfy a trait but we know that the method will always + succeed. + + For example, here's a saner implementation of `FromStr` for `String` than + currently exists in `libstd`. + + ```rust + impl FromStr for String { + type Err = !; + + fn from_str(s: &str) -> Result { + Ok(String::from(s)) + } + } + ``` + + This result can then be safely unwrapped to a `String` without using + code-smelly things like `unreachable!()` which often mask bugs in code. + + ```rust + let r: Result = FromStr::from_str("hello"); + let s = match r { + Ok(s) => s, + Err(e) => match e {}, + } + ``` + + Empty types can also be used when someone needs a dummy type to implement a + trait. Because `!` can be converted to any other type it has a trivial + implementation of any trait whose only associated items are non-static + methods. The impl simply matches on self for every method. + + Example: + + ```rust + trait ToSocketAddr { + fn to_socket_addr(&self) -> IoResult; + fn to_socket_addr_all(&self) -> IoResult>; + } + + impl ToSocketAddr for ! { + fn to_socket_addr(&self) -> IoResult { + match self {} + } + + fn to_socket_addr_all(&self) -> IoResult> { + match self {} + } + } + ``` + + All possible implementations of this trait for `!` are equivalent. This is + because any two functions that take a `!` argument and return the same type + are equivalent - they return the same result for the same arguments and + have the same effects (because they are uncallable). + + Suppose someone wants to call `fn foo(arg: Option)` with + `None`. They need to choose a type for `T` so they can pass `None::` as + the argument. However there may be no sensible default type to use for `T` + or, worse, they may not have any types at their disposal that implement + `SomeTrait`. As the user in this case is only using `None`, a sensible + choice for `T` would be a type such that `Option` can ony be `None`, ie. + it would be nice to use `!`. If `!` has a trivial implementation of + `SomeTrait` then the choice of `T` is truly irrelevant as this means `foo` + doesn't use any associated types/lifetimes/constants or static methods of + `T` and is therefore unable to distinguish `None::` from `None::`. + With this RFC, the user could `impl SomeTrait for !` (if `SomeTrait`'s + author hasn't done so already) and call `foo(None::)`. + + Currently, `Never` can be used for all the above purposes. It's useful + enough that @reem has written a package for it + [here](https://github.com/reem/rust-void) where it is named `Void`. I've also + invented it independently for my own projects and probably other people + have aswell. However `!` can be extended logically to cover all the above + use cases. Doing so would standardise the concept and prevent different + people reimplementing it under different names. + + * **Because it's the correct thing to do.** + + The empty type is such a fundamental concept that - given that it already + exists in the form of empty enums - it warrants having a canonical form of + it built-into the language. For example, `return` and `break` expressions + should logically be typed `!` but currently seem to be typed `()`. (There + is some code in the compiler that assigns type `()` to diverging + expressions because it doesn't have a sensible type to assign to them). + This means we can write stuff like this: + + ```rust + match break { + () => ... // huh? Where did that `()` come from? + } + ``` + + But not this: + + ```rust + match break {} // whaddaya mean non-exhaustive patterns? + ``` + + This is just weird and should be fixed. + +I suspect the reason that `!` isn't already treated as a canonical empty type +is just most people's unfamilarity with empty types. To draw a parallel in +history: in C `void` is in essence a type like any other. However it can't be +used in all the normal positions where a type can be used. This breaks generic +code (eg. `T foo(); T val = foo()` where `T == void`) and forces one to use +workarounds such as defining `struct Void {}` and wrapping `void`-returning +functions: + +In the early days of programming having a type that contained no data probably +seemed pointless. After all, there's no point in having a `void` typed function +argument or a vector of `void`s. So `void` was treated as merely a special +syntax for denoting a function as returning no value resulting in a language +that was more broken and complicated than it needed to be. + +Fifty years later, Rust, building on decades of experience, decides to fix C's +shortsightedness and bring `void` into the type system in the form of the empty +tuple `()`. Rust also introduces coproduct types (in the form of enums), +allowing programmers to work with uninhabited types (such as `Never`). However +rust also introduces a special syntax for denoting a function as never +returning: `fn() -> !`. Here, `!` is in essence a type like any other. However +it can't be used in all the normal positions where a type can be used. This +breaks generic code (eg. `fn() -> T; let val: T = foo()` where `T == !`) and +forces one to use workarounds such as defining `enum Never {}` and wrapping +`!`-returning functions. + +To be clear, `!` has a meaning in any situation that any other type does. A `!` +function argument makes a function uncallable, a `Vec` is a vector that can +never contain an element, a `!` enum variant makes the variant guaranteed never +to occur and so forth. It might seem pointless to use a `!` function argument +or a `Vec` (just as it would be pointless to use a `()` function argument or +a `Vec<()>`), but that's no reason to disallow it. And generic code sometimes +requires it. + +Rust already has empty types in the form of empty enums. Any code that could be +written with this RFC's `!` can already be written by swapping out `!` with +`Never` (sans implicit casts, see below). So if this RFC could create any +issues for the language (such as making it unsound or complicating the +compiler) then these issues would already exist for `Never`. + +# Detailed design + +Add a type `!` to Rust. `!` behaves like an empty enum except that it can be +implicitly cast to any other type. ie. the following code is acceptable: + +```rust +let r: Result = Ok(23); +let i = match r { + Ok(i) => i, + Err(e) => e, // e is cast to i32 +} +``` + +Implicit casting is necessary for backwards-compatibility so that code like the +following will continue to compile: + +```rust +let i: i32 = match some_bool { + true => 23, + false => panic!("aaah!"), // an expression of type `!`, gets cast to `i32` +} + +match break { + () => 23, // matching with a `()` forces the match argument to be cast to type `()` +} +``` + +In the compiler, remove the distinctions that treat diverging and converging +expressions as two different kinds of things (eg. stuff like `FnConverging` vs +`FnDiverging`). Use the type system to do things like reachability analysis. + +Add an implementation for `!` of any trait that it can trivially implement. Add +methods to `Result` and `Result` for safely extracting the inner +value. Name these methods along the lines of `unwrap_nopanic`, `safe_unwrap` or +something. + +# Drawbacks + +Someone would have to implement this. + +# Alternatives + + * Don't do this. + * Move @reem's `Void` type into `libcore`. This would create a standard empty + type and make it available for use in the standard libraries. If we were to + do this it might be an idea to rename `Void` to something else (`Never`, + `Empty` and `Mu` have all been suggested). Although `Void` has some + precedence in languages like Haskell and Idris the name is likely to trip + up people coming from a C/Java et al. background as `Void` is *not* `void` + but it can be easy to confuse the two. + +# Unresolved questions + +Apparently, rust used to have something similar to this but it was removed. +There are still a few references to `ty_bot` in the compiler. Why was this +taken out? Note that if there any arguments for not having type `!` in the +language they should apply equally well to `Never`/`Void` so I assume the old +`ty_bot` was trying to be something crazier than this RFC's `!` (such as a +subtype of all types, given the name). Could someone who was around back then +clarify this? + +`!` has a unique impl of any trait whose only items are non-static methods. It +would be nice if there was a way a to automate the creation of these impls. +Should `!` automatically satisfy any such trait? Alternatively we could do this +through a new trait attribute: + +```rust +#[derive_bang] +trait FromStr { + ... +} +``` + From 4cb013799daf74889321ab382d2e1a7975c4bb07 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Mon, 20 Jul 2015 17:53:54 +0800 Subject: [PATCH 2/6] s/aswell/as well/ --- text/0000-bang-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-bang-type.md b/text/0000-bang-type.md index 72bf837f59c..cea8750f8b6 100644 --- a/text/0000-bang-type.md +++ b/text/0000-bang-type.md @@ -259,7 +259,7 @@ So why do this? AFAICS there are 3 main reasons enough that @reem has written a package for it [here](https://github.com/reem/rust-void) where it is named `Void`. I've also invented it independently for my own projects and probably other people - have aswell. However `!` can be extended logically to cover all the above + have as well. However `!` can be extended logically to cover all the above use cases. Doing so would standardise the concept and prevent different people reimplementing it under different names. From 49453a74cd84a04ff0e2c8231b4954dc0b7faedd Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Sat, 7 May 2016 13:03:23 +0800 Subject: [PATCH 3/6] Small improvements based on thread comments --- text/0000-bang-type.md | 53 +++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/text/0000-bang-type.md b/text/0000-bang-type.md index cea8750f8b6..247ed8fb5d8 100644 --- a/text/0000-bang-type.md +++ b/text/0000-bang-type.md @@ -12,10 +12,11 @@ Promote `!` to be a full-fledged type equivalent to an `enum` with no variants. To understand the motivation for this it's necessary to understand the concept of empty types. An empty type is a type with no inhabitants, ie. a type for which there is nothing of that type. For example consider the type `enum Never -{}`. This type has no constructors and therefore can never be instantiated. It is empty, in the sense that there are no values of type `Never`. Note -that `Never` is not equivalent to `()` or `struct Foo {}` each of which have -exactly one inhabitant. Empty types have some interesting properties that may -be unfamiliar to programmers who have not encountered them before. +{}`. This type has no constructors and therefore can never be instantiated. It +is empty, in the sense that there are no values of type `Never`. Note that +`Never` is not equivalent to `()` or `struct Foo {}` each of which have exactly +one inhabitant. Empty types have some interesting properties that may be +unfamiliar to programmers who have not encountered them before. * They never exist at runtime. Because there is no way to create one. @@ -42,8 +43,9 @@ be unfamiliar to programmers who have not encountered them before. * They represent the return type of functions that don't return. For a function that never returns, such as `exit`, the set of all values it may return is the empty set. That is to say, the type of all values it may - return is the type of no inhabitants, ie. `Never` or anything equivalent to - it. + return is the type of no inhabitants, ie. `Never` or anything isomorphic to + it. Similarly, they are the logical type for expressions that never return + to their caller such as `break`, `continue` and `return`. * They can be converted to any other type. To specify a function `A -> B` we need to specify a return value in `B` for @@ -98,8 +100,8 @@ fn wrap_exit() -> Never { // we can use a `Never` value to diverge without using unsafe code or calling // any diverging intrinsics fn diverge_from_never(n: Never) -> ! { - match n { - } + match n { + } } fn main() { @@ -263,6 +265,20 @@ So why do this? AFAICS there are 3 main reasons use cases. Doing so would standardise the concept and prevent different people reimplementing it under different names. + * **Better dead code detection** + + Consider the following code: + + ``` + let t = std::thread::spawn(|| panic!("nope")); + t.join().unwrap(); + println!("hello"); + + ``` + Under this RFC: the closure body gets typed `!` instead of `()`, the `unwrap()` + gets typed `!`, and the `println!` will raise a dead code warning. There's no + way current rust can detect cases like that. + * **Because it's the correct thing to do.** The empty type is such a fundamental concept that - given that it already @@ -326,6 +342,12 @@ written with this RFC's `!` can already be written by swapping out `!` with issues for the language (such as making it unsound or complicating the compiler) then these issues would already exist for `Never`. +It's also worth noting that the `!` proposed here is *not* the bottom type that +used to exist in Rust in the very early days. Making `!` a subtype of all types +would greatly complicate things as it would require, for example, `Vec` be a +subtype of `Vec`. This `!` is simply an empty type (albeit one that can be +cast to any other type) + # Detailed design Add a type `!` to Rust. `!` behaves like an empty enum except that it can be @@ -352,10 +374,11 @@ match break { () => 23, // matching with a `()` forces the match argument to be cast to type `()` } ``` +These casts can be implemented by having the compiler assign a fresh, diverging +type variable to any expression of type `!`. -In the compiler, remove the distinctions that treat diverging and converging -expressions as two different kinds of things (eg. stuff like `FnConverging` vs -`FnDiverging`). Use the type system to do things like reachability analysis. +In the compiler, remove the distinction between diverging and converging +functions. Use the type system to do things like reachability analysis. Add an implementation for `!` of any trait that it can trivially implement. Add methods to `Result` and `Result` for safely extracting the inner @@ -379,14 +402,6 @@ Someone would have to implement this. # Unresolved questions -Apparently, rust used to have something similar to this but it was removed. -There are still a few references to `ty_bot` in the compiler. Why was this -taken out? Note that if there any arguments for not having type `!` in the -language they should apply equally well to `Never`/`Void` so I assume the old -`ty_bot` was trying to be something crazier than this RFC's `!` (such as a -subtype of all types, given the name). Could someone who was around back then -clarify this? - `!` has a unique impl of any trait whose only items are non-static methods. It would be nice if there was a way a to automate the creation of these impls. Should `!` automatically satisfy any such trait? Alternatively we could do this From ca63eb92da882eae556ba94a5a7cd9b4c706b40d Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Fri, 13 May 2016 00:36:15 +0800 Subject: [PATCH 4/6] Add remark about allow explicit casts from --- text/0000-bang-type.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-bang-type.md b/text/0000-bang-type.md index 247ed8fb5d8..d70e3556c65 100644 --- a/text/0000-bang-type.md +++ b/text/0000-bang-type.md @@ -380,6 +380,9 @@ type variable to any expression of type `!`. In the compiler, remove the distinction between diverging and converging functions. Use the type system to do things like reachability analysis. +Allow expressions of type `!` to be explicitly cast to any other type (eg. +`let x: u32 = break as u32;`) + Add an implementation for `!` of any trait that it can trivially implement. Add methods to `Result` and `Result` for safely extracting the inner value. Name these methods along the lines of `unwrap_nopanic`, `safe_unwrap` or From 00ae686a3da5534ba98a7f2e9e025cf25b5b8a3b Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Sat, 4 Jun 2016 16:02:39 +0800 Subject: [PATCH 5/6] Add reference to RFC 1637 --- text/0000-bang-type.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/text/0000-bang-type.md b/text/0000-bang-type.md index d70e3556c65..61330efa40c 100644 --- a/text/0000-bang-type.md +++ b/text/0000-bang-type.md @@ -407,13 +407,9 @@ Someone would have to implement this. `!` has a unique impl of any trait whose only items are non-static methods. It would be nice if there was a way a to automate the creation of these impls. -Should `!` automatically satisfy any such trait? Alternatively we could do this -through a new trait attribute: - -```rust -#[derive_bang] -trait FromStr { - ... -} -``` +Should `!` automatically satisfy any such trait? This RFC is not blocked on +resolving this question if we are willing to accept backward-incompatibilities +in questionably-valid code which tries to call trait methods on diverging +expressions and relies on the trait being implemented for `()`. As such, the +issue has been given [it's own RFC](https://github.com/rust-lang/rfcs/pull/1637). From 56c73391e12a653344953e6696f6f7268f8f4c06 Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 7 Jul 2016 19:29:56 +0800 Subject: [PATCH 6/6] Small grammar/typo fixes --- text/0000-bang-type.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-bang-type.md b/text/0000-bang-type.md index 61330efa40c..13db77cf612 100644 --- a/text/0000-bang-type.md +++ b/text/0000-bang-type.md @@ -121,7 +121,7 @@ fn main() { ``` This RFC proposes that we allow `!` to be used directly, as a type, rather than -using `Never` (or equivalent) in it's place. Under this RFC, the above code +using `Never` (or equivalent) in its place. Under this RFC, the above code could more simply be written. ```rust @@ -309,7 +309,7 @@ history: in C `void` is in essence a type like any other. However it can't be used in all the normal positions where a type can be used. This breaks generic code (eg. `T foo(); T val = foo()` where `T == void`) and forces one to use workarounds such as defining `struct Void {}` and wrapping `void`-returning -functions: +functions. In the early days of programming having a type that contained no data probably seemed pointless. After all, there's no point in having a `void` typed function