From<bool> for primitive integer types

Hi all,

It has probably already been discussed somewhere but I can’t find anything on this forum nor on google.

Is there any reason why From<bool> is not implemented for all integer types (signed & unsigned) ?

Kind regards

2 Likes

It’s lossless and works through as, so having it in From sure sounds reasonable to me.

Maybe just make a PR and see what libs says?

2 Likes

Indeed, I’m already preparing it :slight_smile:

4 Likes

Sounds right, since wider integer types already implement From<narrower_integer_type> too.

I was trying to find a counterargument; bool isn’t exactly an integer type, semantically speaking, so how can one canonically map true and false to integers, except by convention (false = 0, true = 1).

However, I failed to convince myself that any other mapping would do because of the ring isomorphism to u1: addition = XOR and multiplication = AND. So there’s only one mapping that makes sense, and that mapping is lossless, plus calling .into(), is explicit. I’m out of arguments. Do it.

3 Likes

There are three potential mappings: true/false => 1/0, 0/1, -1/0. Four if you count 1/0/FileNotFound.

Conceptual shortcuts make it easier to make conceptual mistakes.

1 Like

@DanielKeep: As @GolDDranks points out, only two of those are consistent with the fact that | and & work on bool as a 1-bit number: 1/0 or -1/0.

But it’s all academic anyway, since the as operator already defines the meaning of the conversion.

But as defines narrowing conversions, so by that logic, From<u64> for u8 should also be implemented. I’m not saying From<bool> for {integer} should disagree with as; I don’t think bool as {integer} is a particularly good conversion in the first place, so I don’t think it makes the best argument for defining a From implementation in this case.

3 Likes

I’m not sure about this. Bool isn’t a regular number. It has meaning beyond u1, so this conversion could hide bugs, e.g. if you thought a function returns a value, but it only reports success:

let bytes_written: u64 = write(stuff).into();
1 Like

Then should’nt this API use a Result<(), ()> rather than a boolean ?

It should, but not all APIs are like that. Also Result is noisy and may be annoying for calls where it’s not critical to check (eg write to stderr was such case, eprintln doesn’t report now)

My initial incentive for that question is because we often have to extract bits from integer while working on embedded targets. Writing generic for becomes tedious when a function should insert/extract from a register the in the same way for all fields.

It can be worked around with a newtype :

use std::convert::From;
use std::convert::Into;

struct MyBool(bool);
impl From<bool> for MyBool {
    fn from(v: bool) -> MyBool {
        MyBool(v)
    }
}
impl Into<u32> for MyBool {
    fn into(self) -> u32 {
        self.0 as u32
    }
}

fn do_something<T>(v: T) where T: Into<u32> {
    let u: u32 = v.into();
    println!("{}", u);
}

fn main() {
    do_something(MyBool::from(true));
}

But I don’t find it really elegant.

Note that Python, which is considered to be a strongly-typed dynamically typed language, bool is, in fact, a number:

>>> import numbers
>>> isinstance(True, numbers.Number)
True
>>> True + True
2
>>>

I don't think that this causes any problems, and can be sometimes useful.

Which is why I explicitly also said "It's lossless", so no, it's not "by that logic".

1 Like

Why not just: do_something(true as u32);? It's not bad that as is used for non-obvious primitive conversions.

Wow. That's quite the contrast from theory, given that boolean algebra has 1+1=1 since + acts as the OR operator.

It is an example where you cannot assume what type will be used internally by do_something.

And this does not compile :

fn do_something<T>(v: T) {
    println!("{}", v as u32);
}

fn main() {
    do_something(true);
}

Sorry, the actual point I failed to emphasize was that if you have a function with generic type T: Into<u64>, you can just pass in a u64, as the conversion is trivial to write caller-side (b as u64). The use of as is almost beside the point, but it's easier to defend in this case since the conversion is not lossy. A clearer alternative might be to make a more descriptive helper method or inline its trivial implementation:

fn one_if_true(b: bool): u64 { if b {1} else {0} }
...
do_something(one_if_true(true));
do_something(if b {1} else {0});

Generally, an implementation of Into is useful when the intended conversion is obvious (and especially so if its implementation is non-trivial). I agree with @DanielKeep's original comment: mathematically speaking, there's not an obvious canonical choice for conversion from bool, although some languages have settled on false/true -> 0/1.

This example has no constraints on T at all, making v unusable within the body. My point is, if you have T: Into<u64> on the method, just pass in u64 by doing an explicit conversion client-side as that's trivial for bools given a choice of numbers for true and false.

If this can be of any help, this is the usecase that’s driving my reflection.

I have a macro that generates function that needs to convert between some types (that might be bool) to some primitive integer type. If you uncomment line 216 everything breaks simply because bool does not implement from and you cannot implemented it (trait & type defined in another crate).

2 Likes

Thanks, that makes your use-case (and the workaround strategy) clearer. Perhaps you are already aware of this solution, but in case you didn’t try it: you can work around the omission of From<bool> for u32 by adding an extension trait as follows.

trait IntoU32 {
  into_u32(&self): u32;
}

impl<T: Into<u32>> IntoU32 for T {
  into_u32(&self): u32 { self.into() };
}

impl IntoU32 for bool {
  into_u32(&self): u32 { self as u32 };
}

and using into_u32() instead of into() everywhere. Since IntoU32 is local to your crate, it has the semantics that make most sense for your specific crate for bool -> u32 conversion.

1 Like