Need help with boolean type condition

I found some app working unexpectedly when I'm extending condition with simply add | self.write_json_to_file | self.write_bin_to_file part like:

impl StatisticsConfig {
    pub fn active(&self) -> bool {
        (self.interval != 0) & (self.print_to_stdout | self.write_html_to_file | self.write_json_to_file | self.write_bin_to_file)
    }
}

On this example, all values (self.print_to_stdout | self.write_html_to_file | self.write_json_to_file | self.write_bin_to_file) are boolean.

I would prefer to use logical operators || but not sure, why does maintainer use bitwise condition, maybe for performance reasons.

1 Like

There's a clippy lint that prefers the logical operators over bitwise operators: clippy::needless_bitwise_bool.

It says logical operators would be better for performance, but I think that would only matter when the right side is complex, which the lint doesn't even work for. For things like your code, I would expect the branchless nature of the bitwise operators to be faster. I'd also expect the compiler to optimize logical operators the same way, but perhaps it did not here.

I can't think of any reason besides performance[1] for doing this.


  1. I guess code golf is another â†Šī¸Ž

2 Likes

so the true | true is same as true || true?

It'll always give the same answer, yes.

3 Likes

Thank you!

As usual, one should write whatever's most readable in the vast majority of cases.

That said, && and || are short-circuiting, as you know. That's essential in lots of cases -- x != 0 && foo(z / x) and similar -- and a good default for anything non-trivial on the RHS.

But it's control flow, so for a sufficiently trivial RHS it can make it easier on the optimizer to not write short-circuiting, so it knows that it's allowed to just run it and that's ok, without needing to prove it safe to run. (After all, if it's not short-circuiting it's going to run always.)

It looks like LLVM's gotten smarter so my go-to example is now fully optimized either way (https://rust.godbolt.org/z/TnvKdqPfb, at least in x64).

But if you use a compiler from three years ago, then https://rust.godbolt.org/z/EfdzWjn16

pub struct Demo(u16, u16, u16, u16);

#[no_mangle]
pub fn obvious_eq(x: &Demo, y: &Demo) -> bool {
    x.0 == y.0 && x.1 == y.1 && x.2 == y.2 && x.3 == y.3
}

#[no_mangle]
pub fn no_short_circuit_eq(x: &Demo, y: &Demo) -> bool {
    (x.0 == y.0) & (x.1 == y.1) & (x.2 == y.2) & (x.3 == y.3)
}

compiled to

obvious_eq:
        movzx   ecx, word ptr [rdi]
        xor     eax, eax
        cmp     cx, word ptr [rsi]
        jne     .LBB0_5
        movzx   ecx, word ptr [rsi + 2]
        cmp     word ptr [rdi + 2], cx
        jne     .LBB0_5
        movzx   eax, word ptr [rdi + 4]
        cmp     ax, word ptr [rsi + 4]
        jne     .LBB0_3
        movzx   eax, word ptr [rdi + 6]
        cmp     ax, word ptr [rsi + 6]
        sete    al
.LBB0_5:
        ret
.LBB0_3:
        xor     eax, eax
        ret

no_short_circuit_eq:
        mov     rax, qword ptr [rdi]
        cmp     rax, qword ptr [rsi]
        sete    al
        ret

So someone who was micro-optimizing a bit of code like that might have chosen to change the normal && implementation to & in order to get an optimization like that, if it was done at a time when LLVM's optimizer wasn't yet capable of doing it automatically.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.