Proper idiom for cascade boolean checks

I have written a filter_book function taht checks a book against a fitler and returns a boolean. No problem in and of itself. But I wondering if there is a more idiomatic way to write it with better short circuit. Slightly simplified, body starts with:

let mut val = filter.name.is_empty() || book.name.contains(filter.name());

I could then do

if !val { return false };

but if I do that, I end up doing it over and over again.
The rest of the function (until the ending return of val) consists of a series of liens that look like:

val &= (filter.field.is_empty() || book.afield.contains(filter.field));

Is there a better way to structure that? I presume that even if there were a syntactic way to short-circuit it, the result would actually cause a lot more checks for exiting, since I expect most of the fitler fields to be empy, and therefore succeed?
Thanks,
Joel

If every filter.field has the same type and every book.afield has the same type (not necessarily the same as the filter.field type), you can sort of simplify this with a loop over an array of pairs of references, like so:

let fields = [
    (&filter.name, &book.name),
    (&filter.author, &book.author),
    (&filter.subject, &book.subject),
    // etc.
];
for (filtfield, bookfield) in fields {
    if !(filtfield.is_empty() || bookfield.contains(filtfield)) {
        return false;
    }
}
return true;

or, with iterator methods:

let fields = [
    (&filter.name, &book.name),
    (&filter.author, &book.author),
    (&filter.subject, &book.subject),
    // etc.
];
return fields.into_iter().all(|(filtfield, bookfield)| filtfield.is_empty() || bookfield.contains(filtfield));

It seems to me that you are trying to come up with a structure based on statements, when it would be better written as an expression.

fn filter_book(...) -> bool {
    (filter.name.is_empty() || book.name.contains(filter.name()))
        && (filter.field.is_empty() || book.afield.contains(filter.field))
        && ...
}

This way you get short circuiting, and do not need any extra control flow code.

(It would also be good to write a function for the recurring "empty or contains" logic.)

Thanks. Yes, they are all Strings (except one special case I know I need to handle separately.)

I started with that. What bothered me is that it is an unwieldy expression. But maybe I should bite the bullet and just accept that.

For strings specifically, note that

filter.name.is_empty() || book.name.contains(filter.name);

is equivalent to just

book.name.contains(filter.name);

because every string contains the empty string.

Thanks. I had missed that, and it does simplify the code nicely.
Joel

let mut ok = long_line_testing_something();
ok = ok && long_line_testing_something_other();
ok = ok && (long_test_a() || long_test_b());

ok = ok && {
    let mut or = i_run_out_of_names();
    or = or || maybe_this_is_not_a_good_idea();
    or || but_who_knows()
};

ok = ok && here_we_continue();
ok = ok && until_nobody_understand_this_code();