Inference of generic function parameter: workaround (needed)?

Consider this code:

fn f<T, F>(arg: T, check: F) -> bool
where
    T: AsRef<str>,
    F: Fn(T) -> bool,
{
    let word = "bar";
    check(word);

    check(arg)
}

fn g() {
    let _ = f("foo", |s| s.is_empty());
}

The line check(arg) works: arg is of T, so fits the function signature of F, which is simply Fn(T) -> bool.

However, check(word) does not compile:

error[E0308]: mismatched types
 --> src/test.rs:7:11
  |
1 | fn f<T, F>(arg: T, check: F) -> bool
  |      - this type parameter
...
7 |     check(word);
  |     ----- ^^^^ expected type parameter `T`, found `&str`
  |     |
  |     arguments to this function are incorrect
  |
  = note: expected type parameter `T`
                  found reference `&str`
note: callable defined here
 --> src/test.rs:4:8
  |
4 |     F: Fn(T) -> bool,
  |        ^^^^^^^^^^^^^

After some confusion, it dawned on me: while &str implements AsRef<str> (else, the call f("foo", ...) inside of g wouldn't be possible either), T really is unknown and strictly treated as such; it could be anything, and in check(word) I am trying to force T to be &str, which doesn't work.

Fine. Looking it the snippet, T could be inferred as &str in this instance but that of course doesn't work as f could be called from more places, with types other than &str but still implementing AsRef<str>. Second, Rust treats its signatures strictly, so T is T, end of story.

Is there a workaround (needed) for this usage pattern? Any way I can come up with to use check from within f so far failed, as it inevitably ascribes a concrete type to T.

Or am I trying to solve the wrong problem/using an antipattern? I want the user to supply an arg, and also a check/callback to see whether a subpart (this is for working with substrings of arg) is good to go to perform filtering.

(In the above case, word being &str is known statically: F: Fn(&str) -> bool works fine actually, but is less flexible (and I'd love to understand why the above falls flat!).)

The body of f wants to be able to call check with any type that implements AsRef<str>, while the signature of f declares that check can be called with a single type that implements AsRef<str>.

Closures can't do that directly, but you can create your own trait that does, and implement for closures that take an &str. You do have to specify the type of the closure arg though, type inference can't handle cases where the trait isn't directly Fn* a lot of the time

Playground

trait CheckStr {
    fn check<T: AsRef<str>>(&mut self, str: T) -> bool;
}

fn f<T, F>(arg: T, mut check: F) -> bool
where
    T: AsRef<str>,
    F: CheckStr,
{
    let word = "bar";
    check.check(word);

    check.check(arg)
}

fn g() {
    let _ = f("foo", |s: &str| s.is_empty());
}

impl<F: FnMut(&str) -> bool> CheckStr for F {
    fn check<T: AsRef<str>>(&mut self, str: T) -> bool {
        self(str.as_ref())
    }
}
1 Like

It looks like you want the closure itself to be higher-kinded over its argument type. There are no type-level HRTBs in current Rust; higher-ranked logic can only apply to lifetimes as of today.

I'm not sure why taking &str is less flexible. After all, the only capability AsRef<str> grants to your type is to convert it to a &str, so you couldn't do anything more with such a generic closure anyway.

3 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.