Multiple trait bounds with Fn

Hi,

I'm wondering what is the behavior with multiple trait bounds used with Fn? when without using parentheses in the bounds.

For example:

pub fn handle<F>(&self, handler: F) 
where
F: Fn(Request) -> Response + Send + 'static
{
<snip>
}

My question is: in the above, are Send and 'static associated with the whole Fn, or with the return type Response ?

and, a related but different question, if the whole Fn is Send + 'static, does it imply its return type is also Send + 'static ?

Thanks.

With the whole Fn

No. A couple of examples:

  • fn() -> Rc<()> is Send but Rc<()> is not Send
  • for<'a> fn(&'a ()) -> &'a () is 'static, but &'a () is not always 'static (it is only when 'a == 'static)

In nightly you can even implement the Fn trait for your type, and it has no restriction over what the Output type is.

1 Like

Send and 'static are associated with the whole Fn.

Because the return type of Fn must be a Type(struct, enum, etc.) instead of TypeParamBounds(Send, Lifetime, etc.).

Also because the return type is a Type, whether it is Send + 'static depends only on itself. For example:

pub fn handle<F, R>(&self, handler: F) 
where
    F: Fn(Request) -> R,
    R: Send + 'static
{
    unimplemented!()
}

At this time, the return type is Send + 'static.

2 Likes

Because the return type of Fn must be a Type(struct, enum, etc.) instead of TypeParamBounds ( Send , Lifetime, etc.).

How about a Fn that returns a Future, which is a trait, not a Type?

for example:

pub fn handle<F, S>(&self, handler: F) 
where
F: Fn(Request) -> S + Send + 'static,
S: Future<Output = Response> + Send,

In this example, function returns S, which is a Type. The Future is just a bound on what kind of S's can be used here.

1 Like

Functions can only return a specific type, not traits. Generic functions are the same, except that their return type and parameter type can only be determined when they are called.

Your example:

is correct. Its meaning is: F is a closure. Its return type is S. The concrete type of S can only be determined when handle is called, but the restriction is that S must implement the Future trait.

There's an ambiguity if you return impl Trait, but this can be resolved with parentheses:

where
    F: Fn(Response) -> (impl Future<Output = Response> + Send) + Send + 'static,

edit: scratch that, I guess impl Trait isn't allowed there.

I think there might still be an ambiguity with &dyn Trait

1 Like

Especially since dyn is still optional.

E.g.

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: Fn(&()) -> &Display + Send,
{
}

compiles, as does

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: (Fn(&()) -> &Display) + Send,
{
}

both meaning the same as

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: (Fn(&()) -> &dyn Display) + Send,
{
}

On the other hand

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: Fn(&()) -> &(Display + Send),
{
}

is the same as

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: Fn(&()) -> &(dyn Display + Send),
{
}

Finally,

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: Fn(&()) -> &dyn Display + Send,
{
}
error: ambiguous `+` in a type
 --> src/lib.rs:4:20
  |
4 |     F: Fn(&()) -> &dyn Display + Send,
  |                    ^^^^^^^^^^^^^^^^^^ help: use parentheses to disambiguate: `(dyn Display + Send)`

does not compile (even though it did without the dyn).


Fascinating stuff: Even

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: Fn() -> Display + Send,
{
}

compiles, as does

use core::fmt::Display;
fn foo<F>(f: F)
where
    F: Fn() -> str,
{
}

You can’t really call either of these functions though, or their arguments inside the function bodies.

2 Likes

@keepsimple1 FWIW, this is the reason I prefer to put marker traits and lifetime bounds before the Fn… or in a separate line; it makes everything more readable:

fn handle<F>(&self, handler: F) 
where
    F : Fn(Request) -> Response,
    F : Send + 'static,
3 Likes

Isn't FnOnce::Output: Sized?

Yes it is. That’s why I’m saying you can’t call those functions. As far as I understand, trait bounds on associated types are checked when the trait is implemented, not when the trait is used in a bound somewhere. This is different from type parameters for the trait.

Then there’s also builtin types that don’t really have a proper trait implementation anywhere where the compiler makes them implement the trait somewhat on-the-fly as far as I can tell. For example Fn is implemented by dyn Fn(…) -> … trait objects and by closures and functions and function pointers; all built-in types. These builtin types then need to come with special checks of their own to make sure that their “fake”/on-the-fly trait implementation doesn’t violate any requirements such as that FnOnce::Output: Sized. Sometimes they don’t actually come with such checks, for this particular case, here’s a bug report (by myself) of such an example.

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