Storing Closures: To store a closure in a struct, we use a generic type
parameter (here P). This is because every closure in Rust has a unique,
anonymous type generated by the compiler.
Fn Trait Bound: The bound P: Fn(u8, &str) -> bool tells the compiler
that P can be called as a function with the specified arguments and return
type. We use Fn (instead of FnMut or FnOnce) because log takes
&self, so we can only access the predicate immutably.
Calling fields: We invoke the closure using (self.predicate)(...). The
parentheses around self.predicate are necessary to disambiguate between
calling a method named predicate and calling the field itself.
Discuss why Fn is required. If we used FnMut, log would need to take
&mut self, which conflicts with the Logger trait signature. If we used
FnOnce, we could only log a single message!
The impl block for new also includes the bounds. While technically not
strictly required for the struct definition itself (bounds can be placed only
on impl blocks that use them), putting them on new helps type inference.