Set bit of u8: set bit of u8

Is there a better way to write this function ?


    pub fn set_bit(x: u8, idx: usize, b: bool) -> u8 {
        let flag = 1_u8 << idx;
        let is_set = (x & flag) != 0;

        if is_set == b {
            x
        } else {
            x ^ flag
        }
    

I can only think of one other way to write this.

pub fn set_bit(x: u8, idx: usize, b: bool) -> u8 {
    let flag = 1 << idx;
    if b {
        x | flag
    } else {
        x & !flag
    }
}

They're mostly the same, not sure which is better: https://rust.godbolt.org/z/Evqcerqr5

1 Like

This is also possible:

pub fn set_bit(x: u8, idx: u8, b: bool) -> u8 {
    let mask = !(1 << idx);
    let flag = (b as u8) << idx;
    x & mask | flag
}

Not sure, which is better, but this one has no branching.

2 Likes

Yours looks a lot better in the case when bool is known (e.g. through inlining if you pass a literal):

Compiler Explorer


@Tom47's is just as good in that case

Compiler Explorer

Using u32 seems to result in simpler assembly (at least on x86_64 that godbolt uses). It technically changes behavior for idx >= 8, I believe (different wrapping behavior).

pub fn set_bit(x: u8, idx: u8, b: bool) -> u8 {
    let mask = !(1 << idx);
    let flag = (b as u32) << idx;
    (x as u32 & mask | flag) as _
}

Compiler Explorer

pub fn set_bit(x: u8, idx: u8, b: bool) -> u8 {
    (x & !(1 << idx)) | ((b as u8) << idx)
}

I don't think this behaves correctly for b == false

Fixed it.

I see, so it ended up becoming the same kind of logic that @Tom47 shared, too.

I never realized ! flipped all the bits; up until now, I just thought it was negation. :slight_smile:

Looks like we have the same idea.

Seems to me the generated code for this operation should be so tiny that it should always be inlined.

We would expect it to be more efficient without any "if" branches but I suspect the optimisers see through all that and end up generating branchless code anyway. There is likely no point in trying to optimise it in our sources.

Indeed. Through the magic of (unlike C) having an actually distinct proper type for booleans, in Rust, the same operator can be used for logical negation on bool as for bitwise negation on integers. (If you want "logical negation" on integers, anyways, you'd spell that x == 0).