rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Make `bool` an `enum`

Open aturon opened this issue 11 years ago • 38 comments

Currently, bool is not an enum, but there's no strong reason for that state of affairs. Making it an enum would increase overall consistency in the language.

This is a backwards-compatible change (depending on the specifics) and has been postponed till after 1.0.

See https://github.com/rust-lang/rfcs/pull/330.

aturon avatar Oct 02 '14 22:10 aturon

Whatever happened to this? =P

(is it still backwards compatible?)

Centril avatar Aug 23 '18 22:08 Centril

We document:

https://doc.rust-lang.org/1.34.0/std/primitive.bool.html

If you cast a bool into an integer, true will be 1 and false will be 0.

Casting here likely refers to the as operator.

However as far as I can tell we currently make no promise about the memory representation of bool (other than size_of being 1 byte). We may want to keep it that way, for https://github.com/rust-lang/rfcs/pull/954.

If bool is a primitive rather an enum, we can (more easily) special-case its casting behavior in the language.

SimonSapin avatar Apr 23 '19 13:04 SimonSapin

Wouldn't the casting behaviour and guaranteed size_of still be possible with repr(u8)? (even though that might be a problem with #954)

#[repr(u8)]
enum bool {
    false = 0,
    true = 1,
}

oberien avatar May 20 '19 00:05 oberien

The problem with representing bool as an enum is that it's either backwards-compatible (bool, true, false) or it conforms to the CamelCase naming convention for enums defined in RFC #430 (Bool, True, False), not both.

gorilskij avatar Jul 10 '19 17:07 gorilskij

@gorilskij I don't see how this is a big issue. Obviously we have to preserve true and false and be inconsistent with CamelCase How is this that bad though?

RustyYato avatar Jul 10 '19 20:07 RustyYato

@KrishnaSannasi I would tend to agree, both for compatibility and because bool is a basic enough type to warrant a lower case name. It's not bad, just unpleasantly inconsistent, especially in a language as relatively polished as Rust. It's not nice to have a builtin type that, were you to write it yourself, would raise a compiler warning.

There is another, more serious point. As proven by the fact that false is 0 and true is 1, bool is more than just a simple 2-variant enum, it has a u1 kind of numeric feeling to it, logical operations have a numerical operation feeling to them as well as evidenced by some of the notation (&& ~= *, || ~= +, ...) not to mention the fact that all those operations as well as the if/else construct depend on the bool type, it would be possible to do logic without all of that but with how fundamentally useful it is, I think it (bool and friends) deserves it's special status in Rust as in the vast majority of other languages.

As an example to illustrate what I mean, Haskell implements Bool as a true 2-variant type and defines the infix operators (&&, ||, ...) in the standard way to go along with it (note that logical not is called not, not ! to maintain consistency because it's a prefix operator, though Haskell does still bend the rules for prefix - but that's beside the point). This, I believe, is nice and idealistic but not suited to a more practical language like Rust.

TL;DR I think making bool an enum changes nothing from the implementation perspective and goes half-way in a direction I'm not sure we should be going from the purity perspective.

gorilskij avatar Jul 11 '19 16:07 gorilskij

@gorilskij

There is another, more serious point. As proven by the fact that false is 0 and true is 1, bool is more than just a simple 2-variant enum, it has a u1 kind of numeric feeling to it

I would argue that a u1 is just a 2-variant enum, and that any numeric properties it has are just semantics for us, but don't really need to be encoded into it's representation.

Also a bool can be represented as @oberien showed earlier,

#[repr(u8)]
enum bool {
    false = 0,
    true = 1
}

and then special casing && and ||. The other operators (like !) can be implemented normally.

Note that there is movement to make && and || overloadable like all of the other operators. If that lands, then we can implement && and || normally for bool as well.

One more advantage to this is that it reduces the number of keywords in Rust, because ~~bool~~, true and false no longer have to be keywords.

edit: bool is not a keyword, my bad

RustyYato avatar Jul 11 '19 16:07 RustyYato

Perhaps you're right. I still think that in an ideal world it would be Bool, True and False, maybe that's something to consider for a future edition. What about if/else though? If bool is made to look like any other 2-variant enum won't it seem strange that it alone can be used in those statements? Opening up if/else to any 2-variant enum seems even more weird and magical.

gorilskij avatar Jul 11 '19 16:07 gorilskij

What about if/else though? If bool is made to look like any other 2-variant enum won't it seem strange that it alone can be used in those statements? Opening up if/else to any 2-variant enum seems even more weird and magical.

Well, if and else are kinda special. We could define that

if $cond {
    $block
}

gets desugared to

match $cond {
    bool::true => $block,
    bool::false => (),
}

And similarly for if $cond { $block_t } else { $block_f } we could desugar to a match, this way it is still clear what is happening.

I still think that in an ideal world it would be Bool, True and False, maybe that's something to consider for a future edition

I don't think an edition can do this, it seems like to big of a breaking change.

RustyYato avatar Jul 11 '19 16:07 RustyYato

Alternately, if $cond could be desugared to if let bool::true = $cond, which is more consistent and also works for match guards (although if let match guards are not actually implemented yet).

edit: Actually, that's a bad idea because it conflicts with the proposal to make let an expression.

comex avatar Jul 11 '19 22:07 comex

I understand that if and if/else can be treated as pure sugar but they are nonetheless (together with while which, as far as I can see, can't be desugared without recursion) very fundamental constructs in the language and very fundamentally depend on the bool type. Making the type an ordinary enum seems somehow dirty as it would lose it's intrinsic specialness yet retain a lot of special treatment, making it, as far as I can tell, the only enum "allowed" (without compiler complaint) to have a lowercase name seems like more of the same. I like reducing keywords and removing special behavior as much as anyone but I think this proposal does that too superficially.

gorilskij avatar Jul 13 '19 09:07 gorilskij

if, while, for and co. are already desugared to loop + match.

CryZe avatar Jul 13 '19 09:07 CryZe

“Language items” are not dirty, they are often necessary. For example for loops need to know about the IntoIterator and Iterator traits, as well as the Option enum.

I think there is no real blocker to making bool an enum. We can make it work. But I also don’t see much benefits, so it may not be worth the bother.

SimonSapin avatar Jul 13 '19 09:07 SimonSapin

But I also don’t see much benefits, so it may not be worth the bother.

I don't see any benefits other than "it looks cleaner" which I'd argue it doesn't due to, at least, the naming convention inconsistency.

gorilskij avatar Jul 13 '19 11:07 gorilskij

No-one is suggesting we change the name. Using an enum instead of a bool would simplify some special-casing in the compiler, which would be an advantage.

varkor avatar Jul 13 '19 17:07 varkor

I was talking about the issue purely from a user's point of view, I have no knowledge of compiler internals (or experience in this area for that matter). Just out of curiosity, how would switching to an enum representation help? It seems to me that a primitive type would be easier to write special cases for.

(btw, if this is not the place for such discussion, tell me, I'll move it somewhere else)

gorilskij avatar Jul 14 '19 19:07 gorilskij

@gorilskij as @varkor said it would reduce special casing the compiler, so it would make the compiler simpler. Special casing only makes things more complex, not the other way around. For some things the complexity is justified, but with bool we could easily make it less special and thus simplify the compiler. This makes it easier to maintain and contribute to.

RustyYato avatar Jul 14 '19 21:07 RustyYato

Oh, sorry, I misread it as "it would make it simpler to implement special casing." Thanks for clarifying.

gorilskij avatar Jul 15 '19 05:07 gorilskij

@varkor What's the way forward on this? It seems like it's a good idea, given that it would reduce the special-casing in the compiler.

jhpratt avatar Sep 13 '19 22:09 jhpratt

@jhpratt: there was a brief discussion on Discord about it a while ago. You'd probably want to talk to @oli-obk or @eddyb about it if you wanted to pursue it, who both had some ideas about it.

varkor avatar Sep 16 '19 10:09 varkor

After a slight bit of discussion on Reddit, a couple things came up. Would the true and false keywords need to be removed? Intuitively, I don't think it would be possible to implement an enum bool { false, true } unless they weren't keywords (as otherwise raw identifiers would be necessary). Also, would removing keywords be backwards compatible?

I'll (finally) reach out on Discord to them to see if they have any ideas.

jhpratt avatar Dec 11 '19 22:12 jhpratt

We can keep the keywords and have them refer to the variants of the enum. There might be some diagnostics changes to work out, but I don't think it'd be a problem.

varkor avatar Dec 14 '19 09:12 varkor

I think it's mostly compiler-internal work that is blocking this. I tried it once and it's not super simple to do. Loads of code expects to know that something is a bool or make something a bool, and for this we right now have a very convenient way (tcx.types.bool). With this PR that would have to change to tcx.type_of(tcx.require_lang_item(lang_items::BoolEnum)). While we can totally abstract this to tcx.mk_bool(), it's still not the zero cost thing we had before and I think there were other inconveniences, too. So maybe there could be a preliminary PR that just removes tcx.types.bool in preparation of this change, so we can judge some of the fallout.

oli-obk avatar Dec 14 '19 10:12 oli-obk

I have doubts that this "reduce special casing the compiler", bool is indeed special and built-in for the compiler in many senses. I suspect it would be much easier to offload e.g. ! to the library (as an empty enum) than bool.

petrochenkov avatar Dec 14 '19 10:12 petrochenkov

I'd think the similarities appear only in the nich layout stuff, so maybe some future layout code would be simpler if it could optimize some bools as enum discriminants.

At present, we always permit fields references, which imposes alignment constraints, and makes the layout less interesting. We pack enum discriminants into left over space because one cannot take a separate reference to the discriminants. I'd think composite types could conceivably restrict references to specific fields with some notation like !ref T or #[no_ref] T or #[packed] T.

As an example, size_of::<(bool, bool)>() = 2 while size_of::<(!ref bool, !ref bool)>() = 1, and ideally even size_of::<(!ref Option<bool>, !ref Option<bool>)>() = 1, but you cannot call &self methods on a !ref Option<bool> without first destructuring. You might conceivably treat Copy types even more special here, not sure. It's likely folks would prefer bitfield notations like in C for this anyways.

At first blush anything like this sounds like an internal compiler concern, so not an issue worth considering right now.

burdges avatar Dec 14 '19 14:12 burdges

Layout considerations are completely orthogonal to whether bool becomes an enum or not. If bool keeps not being an enum, this is just a few lines of code in a very encapsulated piece of code that would be affected for layout changes.

oli-obk avatar Dec 14 '19 14:12 oli-obk

I think that 2014 or earlier would have been a good time for a change like this.

At this point doing it without breaking things would take non-trivial care while the benefits as far as I can tell are entirely theoretical. It would feel nice to have fewer special cases in the language, but is there any concrete benefit to users?

I think that formally deciding not to make this change (as opposed to it being merely postponed) should be an option on the table for @rust-lang/lang.

SimonSapin avatar Dec 15 '19 00:12 SimonSapin

I would personally like to avoid considering whether bool should become a library type right now. I would also like to like to avoid making a formal decision that it should never become a library type as well. I'm personally happy with indecision for the foreseeable future. :)

Centril avatar Dec 15 '19 00:12 Centril

@SimonSapin What's the harm in leaving this open, in case someone wanted to at least try it to see the admittedly uncertain benefits? That's what I was going to do, but @Centril said it would likely conflict with other ongoing work.

jhpratt avatar Dec 15 '19 00:12 jhpratt

it would likely conflict with other ongoing work

It sounds like you answered your own question?

SimonSapin avatar Dec 15 '19 09:12 SimonSapin