Iterator methods not acting as I expect

I have a function which needs to determine whether all letters in an input is uppercase. Why does the works() version work, but the fails() version fail when I feed it a string without any alphabetic characters? More specifically, the following three test strings all return true while it should return false:

"1, 2, 3"
"4?"
":) ?"

Here is the code:

fn works(message: &str) -> bool {
    let has_letters = message.chars().any(char::is_alphabetic);
    has_letters && !message.chars().any(char::is_lowercase)
}

fn fails(message: &str) -> bool {
    message.chars().all(|c| !c.is_alphabetic() || c.is_uppercase())
}

(Playground)

2 Likes
fn all<F>(&mut self, f: F) -> bool
where 
   F: FnMut(Self::Item) -> bool

Tests if every element of the iterator matches a predicate.

fn any<F>(&mut self, f: F) -> bool
where 
    F: FnMut(Self::Item) -> bool 

Tests if any element of the iterator matches a predicate.

This means that this doesn't do the same as the first one.

  • The first one (works) makes sure that there's at least one char which is both alphabetic and uppercase.
  • The second one (fails) makes sure that all of the characters are not alphabetic or are uppercase.

Therefore if you wanted fails to do the same as works you'd write:

fn fails_but_works_now(message: &str) -> bool {
    message.chars().any(|c| c.is_alphabetic() && !c.is_lowercase())
}
3 Likes

Thanks! I understand what went wrong with fails() now. It returned true for a string with no alphabetic characters as well, while it should actually check that at least one character in the string is alphabetic.

BTW, your fails_but_works_now() function doesn't do the same as works(). works() makes sure that there is at least one alphabetic character and exactly no lowercase characters in the input string. fails_but_works_now() tests that there is at least one uppercase alphabetic character, but doesn't return false on input where there are lowercase characters.

1 Like

Ah yes, I realize that now, perhaps there's a better way to do it with fold?

You can do it with fold, but it's probably better to do it with try_fold if you want the short-circuiting on lowercase characters:

fn also_works(message: &str) -> bool {
    message
        .chars()
        .try_fold(false, |acc, c| {
            if c.is_lowercase() {
                None
            } else {
                Some(acc || c.is_alphabetic())
            }
        })
        .unwrap_or(false)
}
4 Likes

Thanks! I learnt something today. But I think my first works() function is both shorter and clearer than try_fold(), so I'll stick to that for this specific problem

2 Likes