Bool behavior for 2+, i.e. illegal values

I noticed in Dlang, most recent version:

Compiler changes

  1. bool values other than 0 or 1 are not @safe

The spec has been updated so that only 0 and 1 are safe values for bool. This means that reading a bool value whose underlying byte representation has other bits set is implementation-defined and should be avoided. Consequently:

So got me curious what Julia does, and it seems whatever we do we have to live with it:

julia> reinterpret(Bool, UInt8(0))
false

julia> reinterpret(Bool, UInt8(1))
true

julia> reinterpret(Bool, UInt8(2))
false

julia> reinterpret(Bool, UInt8(3))
true

I would have though all non-zero should be trueā€¦ and simple to check. Only the last bit seems to be ā€œcheckedā€ with and al, 1. See: @code_native reinterpret(Bool, UInt8(2)).

I believe itā€™s intentional to have Bool in 8 bits, not 1, in many languages including C++. But non-zero not always might be true in some other languages, or not done the same, so what are the pros and cons of either/any decision? The and is simple, but most often you test with jne (including in Julia?), so might Julia have a bug?

People arenā€™t supposed to construct other than 0 and 1 Bools, so can it be disallowed/deprecated? Iā€™m not sure it can however be stopped without speed penaltyā€¦ and would be a breaking change to fail when seeing anything else. Or was that not promised behaviour to work?

The reinterpret docstring contains a warning:

help?> reinterpret
  ā”‚ Warning
  ā”‚
  ā”‚  Use caution if some combinations of bits in Out are not considered valid and would otherwise be
  ā”‚  prevented by the type's constructors and methods. Unexpected behavior may result without
  ā”‚  additional validation.
4 Likes

Perhaps related:

2 Likes

Julia has unsafe operations and undefined behaviour, but has not really defined what is safe and what isnā€™t. The meaning of unsafety and undefined behaviour is mostly (only?) known to a handful of core devs.

However, a recent PR explicitly mentions that creating Bool values with other bitpatterns than 0x00 and 0x01 to be undefined behaviour: Add devdocs on UB by Keno Ā· Pull Request #54099 Ā· JuliaLang/julia Ā· GitHub

6 Likes
julia> Bool(2)
ERROR: InexactError: Bool(2)
Stacktrace:
 [1] Bool(x::Int64)
   @ Base ./float.jl:251
 [2] top-level scope
   @ REPL[15]:1

reinterpret is for bit twiddling.

5 Likes

Julia is a mostly safe language by default, i.e. excluding [ccall (or code depending on packages usingā€¦ so basically all code), and @inbounds and], yes, reinterpret, then I think only for Bool (and only when ā€œillegallyā€ constructed; or on Big-endian, not a real worry).

Iā€™m just thinking what was Dā€™s reasoning, and would it apply to Julia?

I shorten the quote, missing this part from D:

  • void initialization of booleans is now deprecated in @safe code.
  • Reading a bool field from a union is now deprecated in @safe code.

And background:

Safe Values

A variable or field marked as @system does not hold a safe value, regardless of its type.

For a bool, only 0 and 1 are safe values.

For all other basic data types, all possible bit patterns are safe.

A pointer is a safe value when it is one of:

  1. null
  2. it points to a memory object that is live and the pointed to value in that memory object is safe.

Julia doesnā€™t have safe or unsafe regions of code (or then implied), like D and Rust, but could maybe.

They have @safe (and @nogc), a bit better than Julia, I think you need to opt into that (not sure) so actually D is historically an unsafe language like C++, but trying to be safe, has such a subset (Iā€™m not sure if you can enable it globally), I think Julia might have an advantage, by being safe be default, except by how often in practice unsafe (and not easy to undo that or forbid that):

9.10 @safe, @trusted, and @system Attributes

1 Like

Iā€™m not sure this is related to this issue, but I talked with @gbaraldi at JuliaCon, and it is unclear whether thatā€™s worth it. There are points where a conversion to i8 are inevitable also in C, I seems to remember Gabriel came up with a C example involving pointers where also Clang was uplifting a boolean to i8, but now I donā€™t remember the details.

But why do you want to use reintrepret at all to get a boolean value out of another integer? You even had to wrap it in UInt8 (and good luck converting 256 with your approach) because thatā€™s not really the intended way to do it. Unless you expect

julia> reinterpret(Int64, 3.0)
4613937818241073152

to return 3.

2 Likes