Reserve `never` for 2018 epoch

Hi there.

There’s an RFC which promotes ! to a type, so it will be possible to write, e. g.

let _: ! = unreachable!();

I’d like to propose

  • reserve never keyword (or soft keyword, or lang item) for the 2018 epoch
  • (or nothing)

Remaining items are not critical while RFC 1216 is not ready:

  • use never keyword for the unreachable type
  • do not allow ! anywhere except in return type
  • allow ! in return type for backward compatibility

Why keyword

The principle is that punctuation should be used for the most frequently used language constructs, and keywords for less frequently used.

I’ve counted in rust sources, it has only 160 usages of ! as type (including tests)

rg -F -- '-> ! {' | wc -l

For comparison, Option is used 15703 times. str is used 10309 times.

Personally, I have a couple of better (that unreachable type) use cases for !, e. g.

  • ! for unwrap, e. g. file.write()! to be equivalent of file.write().unwrap(), which would be useful in scripts, e. g. build.rs
  • ! for force matching, e. g. let! Ok(count) = file.write(); to be equivalent of panicing of function returns other variant than Ok(_)

But concrete applications of ! are not important right now, my point is that we shouldn’t stabilize new use of !, because it will be hard to unstabilize it and reuse ! for something different later.

RFC 1216

Technically, this alternative is mentioned in RFC 1216.

I’m raising this issue now because if never should be a keyword, it should be reserved before epoch 2018, and the decision whether the unreachable type should be ! or a keyword could be done later.

CC @nikomatsakis @Centril

2 Likes

Note that str is not currently a keyword. I believe never and str are roughly in the same boat when it comes to how tied into the language and grammar they are.

fn main() {
    let str = 0;
}

To my knowledge not being a keyword does not generally cause problems related to str. That makes me question whether making never a keyword is worthwhile.

No comment on using never over !, just that this isn’t what keywords are for.

10 Likes

Part of the reason for having the type be ! is that it should become common – types like Result<T, !> to represent a failible operation that doesn’t fail (such as in generic code).

As a side note, I think counting occurrences of type ! before it’s stable is a bad indicator of how widely used it would be. You should also check for any enum Never {} as that serves the same purpose; ! would serve to unify that to a common, known type.

But you should know that, that’s the motive given in the RFC.

6 Likes

Unless I'm missing something, these are both expression contexts, not type contexts. The ambiguity you'd need to solve is vs. the negation operator ! and macros, not the type.

6 Likes

See also Void: void - Rust

3 Likes

Note that we’ll no longer reserve any keywords or syntax for unimplemented ideas, which is why the throw/fail and delegate keyword-reservation RFCs are FCP-closed. Reserving never to allow repurposing ! falls into the same category.

Furthermore, the never type has no syntactical conflict with your use cases. They do conflict with macro invocations however.

macro_rules! a {
    () => {}
}
fn g(a: Option<fn()>) {
    a!()
}
9 Likes

One thing to consider: The unit type () is also written using punctuation. I think the never type plays a similarly fundamental role since it's the "identity element" for sum types just like the unit type is the "identity element" for product types.

5 Likes

Not really. The "Unit Type" is actually just an empty tuple.

Yes, which is exactly why it’s written using punctuation…?

4 Likes

You should also check for any enum Never {} as that serves the same purpose

There's no enum Never in rust sources (and I personally rarely use void trait), that's why I think it won't be used very frequently.

Furthermore, the never type has no syntactical conflict with your use cases. They do conflict with macro invocations however.

It doesn't matter. My point is that reserving ! now would complicate future applications of ! syntax for different things.

(Also, I have to point, that there are already syntax ambiguities in the parser, and that's fine).

I’m not a fan of making the never type named by a str-like type rather than by ! for a few reasons (this is assuming we don’t make never a keyword, since there is no prior art for keywords-that-name-types).

  • As mentioned above, (), the unit type, is also spelled with all-punctuation. While this is merely a consequence of it being the zero-element tuple, I’ll point out that () is slightly more special than your garden-variety tuple, since it’s the return value of a block that doesn’t end in an expression, and the return type of functions without ->.
  • ! is far more fundamental than something like str, to the point that it should not be possible to shadow it by defining your own type called never.
  • ! is a more important type than it’s given credit for, since it’s not a “real type” in stable yet. The main use-case I care about is disabling enum variants, e.g. Result<T, !>, and I expect that adopting it into stable, and encouraging its use, will make it a more common syntax.
  • The use cases you’re interested in are not affected by ! being a type. Those can be added to the grammar since ! only has this meaning in type context.

After writing this I realized that these are mostly points made elsewhere in the thread, but I think it’s worth emphasizing them.

9 Likes

! is far more fundamental than something like str, to the point that it should not be possible to shadow it by defining your own type called never.

Even the argument about shadowing is valid, never could be made a keyword, and it won't be possible to shadow it.

A few thoughts here:

  • This definitely shouldn’t be a keyword. Even u32 isn’t a keyword.
  • Given a time machine, I’d be happy with just enum Never{} in core somewhere.
  • But given that -> ! is stable, having ! be a type makes sense for extending to Result<!, io::Error> or whatever.
6 Likes

One of the reasons ! doesn’t have many usages is because it’s stuck in a state of being perpetually unstable. I use the void crate in most my libraries. It has 60 dependent crates even though it just defines enum Void {}.

2 Likes

I’d want ! to be used for into:

fn f() -> Result<A, B> {
    A::new()!
}

for ambiguity with macros, if followed by () or {} or [] is macro invokation, use () to turn into value (anyway (object!)() and stuff aren’t semantically valid - x.into()() doesn’t compile IIRC)

With a return type of Result, you'd probably rather use ? anyway, but I guess that's not what you actually meant. In any case, what about:

*A::new()!=*B::new()

Is that unwrap followed by assignment, or a not-equals comparison?

Not to argue against the notion itself, mind you! I, too, like the idea of a hard unwrap operator. But this all is orthogonal to the ! type. You could have that unwrap operator (after ironing out the ambiguities), and also still have a ! type. They are used in different places in the code (until Rust gets dependent types, perhaps :wink: ).

4 Likes

Not even. There's already situation in which tokens, such as +, mean different things in ty context v expr context. IIRC, for const generics, most expressions need to be wrapped in {} when used in type position: Array<T, {2 + 2}>.

ofc != would be a greedy token (i.e. parser prefers != over ! =, add space or parens to disambiguate)

also I’m not proposing unwrap! - I’m proposing into! it’s the inverse of ?

? unwraps, ! wraps ok, !! could be used to wrap error if someone wanted that?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.