Loop for logic, not looping

As I code by imagination, I came up with this type of logic

fn main() {
    if loop {
        #[cfg(feature = "cats")]
        if cats_meow() {
            break true;
        }
        #[cfg(feature = "dogs")]
        if dogs_bark() {
            break true;
        }
        #[cfg(feature = "roosters")]
        if rooster_crow() {
            break true;
        }
        break false;
    } {
        println!("Sound");
    } else {
        println!("Silence");
    }
}

And I wonder if that is just plain silly. I don't mean the specific feature and function names being silly, but the logic. Using a loop, not for looping but just for breaking out of it with a value?
I mean, I have a list of cfg's and checking their related functions true/false state. And want to check if they are all true or all false. I would rather just chain them with an if using "&&" but could not get the syntax to work with the cfg's.

    if #[cfg(feature = "cats")]
    cats_meow()
        && #[cfg(feature = "dogs")]
        dogs_bark()

And rustfmt makes that ugly anyway.
What is the idiomatic way to check a list of conditionals that are included with features cfg's?

You could use a closure+returns instead.
Personally dislike seeing } { and tend to add another variable for such block.

1 Like

You're definitely not the only one to come up with that loop-break trick. It was discussed extensively in 2046-label-break-value - The Rust RFC Book for example.

That said, maybe you want a macro like

One alternative I could come up with so far:

    if match () {
        #[cfg(feature = "cats")]
        _ if cats_meow() => true,
        #[cfg(feature = "dogs")]
        _ if dogs_bark() => true,
        #[cfg(feature = "roosters")]
        _ if rooster_crow() => true,
        _ => false,
    } {
        println!("Sound");
    } else {
        println!("Silence");
    };

Another thing that seems to work

    macro_rules! cfg_then_else {
        ($feature: literal, $e:expr, $alt:expr) => {{
            #[cfg(feature = $feature)]
            {
                $e
            }
            #[cfg(not(feature = $feature))]
            {
                $alt
            }
        }};
    }

    if cfg_then_else!("cats", cats_meow(), false)
        || cfg_then_else!("dogs", dogs_bark(), false)
        || cfg_then_else!("roosters", rooster_crow(), false)
    {
        println!("Sound");
    } else {
        println!("Silence");
    }

Can you show me how?

Thanks for reminding me, I will check it out and see how it works in this situation. I did try to use it in the past at a couple of other code points where I have a list of cfg's in function signature generic traits. The cfg-if macro borks in function signatures.

Oh, that is kind of interesting. I had not thought of using match. It seems mentally wrong to have more than one arm with "_", but still interesting how it works.

The macro solution you posted is seems to work, but the invocation is confusing for my brain to read.

If the functions are always defined which admittedly is probably not likely when using features, this is another option. I would think the function calls for missing features would get optimized away (which seems to be confirmed by godbolt for this simple example)

fn main() {
    if (cfg!(feature="cats") && cats_meow())
        || (cfg!(feature="dogs") && dogs_bark())
        || (cfg!(feature="roosters") && roosters_crow())
    {
        println!("Sound");
    } else {
        println!("Silence");
    }
}

I think the most obvious hasn't been said yet. I don't personally agree that you should play tricks with loops, labels, or even closures in this particular case. If you have some non-trivial configuration and logic going on, then simply extract it into a function, which makes it trivial to return early if that's what you need. That will be much easier to understand later.

fn maybe_sound_animal() -> bool {
    #[cfg(feature = "cats")]
    if cats_meow() {
        return true;
    }
    #[cfg(feature = "dogs")]
    if dogs_bark() {
        return true;
    }
    #[cfg(feature = "roosters")]
    if rooster_crow() {
        return true;
    }

    false
}

fn main() {
    if maybe_sound_animals() {
        println!("Sound");
    } else {
        println!("Silence");
    }
}
4 Likes

Personally I think "loop" has an intended meaning. That is to do something multiple times. This is what it communicates to anyone reading your code. Given that your code only ever does one thing once it is misleading to any reader to wrap that up in a loop.

Similarly with other language constructs. In general one should try and express what it is one is wanting to do. Not obfuscate it with language constructs intended for other purposes.

Thank you to you and @H2CO3 for your firmness. After sawing wood and pounding nails for a couple of days, your advice's bouncing through my head. And clippy yelled at me too for using a loop that does not loop. I rethough things. Keep it simple So after I left the code alone and came back to it I do not need any 'loop" that does not loop and my cfg's are much less. I did not even need to put anything into a function. I did not

and things are much clearer.

Also this point really helped me get focused.

Just want to say thanks again for the support and thanks for your patience.
Rust. Empowering everyone even space cadets..

1 Like