“one type is more general than the other”, what type to write

Hi. A function returns a closure to another function:

use std::borrow::Cow;

fn encoding_override_bytes<'r, 'name: 'r, 'value: 'r>(
    name: &'name [u8],
    value: &'value [u8],
) -> impl Fn(&str) -> Cow<'r, [u8]> {
    |a| if a.is_empty() { name.into() } else { value.into() }
}

fn main() {
    let mut serializer = form_urlencoded::Serializer::new(String::new());
    let f = encoding_override_bytes(b"name=\xFF", b"value");
    dbg!(serializer
        .encoding_override(Some(&f))
        .append_pair("", "0")
        .finish());
}

Compilation of the above program yields an error:

error[E0308]: mismatched types
  --> src/main.rs:14:33
   |
14 |         .encoding_override(Some(&f))
   |                                 ^^ one type is more general than the other
   |
   = note: expected enum `Cow<'_, _>`
              found enum `Cow<'_, _>`

Changing to encoding_override(Some(& move |a| f(a))) make it compile. My question is not how to compile it, but what the result type of encoding_override_bytes should be. move |a| f(a) is just an eta-expansion of f, so their types should be equal. The error message suggests that their types are different, but is unhelpful otherwise.

“Cargo.toml”:

[dependencies]
form_urlencoded = "1.2.1"

The closure that ends up as an argument to encoding_override must satisfy this signature:

Fn(&str) -> Cow<'_, [u8]>
// Also known as
for<'a> Fn(&'a str) -> Cow<'a, [u8]>

That means that the lifetime in the returned Cow must be the same as the input argument, and that all possible input lifetimes must be supported.

In general, the closure returned from this function cannot meet that bound:

fn encoding_override_bytes<'r, 'name: 'r, 'value: 'r>(
    name: &'name [u8],
    value: &'value [u8],
) -> impl Fn(&str) -> Cow<'r, [u8]> {

Because, for example, the input could be a &'static str but the output can't live longer than 'r.

When you actually call this function in your OP, however, 'name and 'value are 'static. So let's examine that case:

fn encoding_override_bytes(
    name: &'static [u8],
    value: &'static [u8],
) -> impl Fn(&str) -> Cow<'static, [u8]> {
    |a| if a.is_empty() { name.into() } else { value.into() }
}

You get the same error, only 'static is mentioned specifically now. Why? Because your the return of encoding_override_bytes can only be counted on to implement the stated trait, where 'static specifically is in the return type, and nothing else (beyond auto-traits). In fact, due to how closure inference works, it doesn't implement the required Fn(&str) -> Cow<'_, [u8]> trait.[1]

Why does adding an intermediate closure work? Because it provides a place where the returned 'static lifetime can coerce to any shorter lifetime, including the input lifetime.

Alternatively, now that the inputs to encoding_override_bytes are 'static, you could change the signature to match what is required by encoding_override.

fn encoding_override_bytes(
    name: &'static [u8],
    value: &'static [u8],
) -> impl Fn(&str) -> Cow<'_, [u8]> {
    |a| if a.is_empty() { name.into() } else { value.into() }
}

(Playground.)

If the inputs to encoding_override_bytes are not 'static, this cannot work -- and neither does the intermediate closure. The reason is the same as why the OP didn't work: The input could be 'static, but the output cannot be 'static.


In summary, Fn(&str) -> Cow<'static, [u8]> is a different capability than Fn(&str) -> Cow<'_, [u8]>, but an intermediate closure can implement the latter by calling the an implementor of the former and coercing the returned type.

And a Fn(&str) -> Cow<'specific_but_not_static, [u8]> cannot be so "wrapped" to provide the required Fn(&str) -> Cow<'_, [u8]> capability, because it doesn't support the input-is-a-&'static str case.


  1. Trait bounds do not consider variance. ↩︎