What is the difference between bounds in where and inline?

related - Difference between bounds inside a where clause and "inline" bounds

Having trouble understanding the following.

pub fn fn1<Fut1,Fut2>(
    a:impl Fn() ->Fut1,
    f: impl Fn() -> Fut2)
    where
    Fut1:std::future::Future+'static+std::marker::Send,
    Fut2:std::future::Future,
 {}

pub fn fn2<Fut1,Fut2>(
    a:impl Fn() ->Fut1+'static+std::marker::Send+'static,
    f: impl Fn() -> Fut2)
    where
    Fut1:std::future::Future+'static+std::marker::Send,
    Fut2:std::future::Future
 {}

Apparently those 2 functions are not the same. Running fn1 in a thread requires Fut1 to implement Send.

     F: Send + 'static,
    |        ^^^^ required by this bound in `std::thread::spawn`
help: consider further restricting this bound

But it already does in the where clause I thought. It seems the where clause just says please give this kind of object and in the fn params I need to specify the object where is expecting. So this must be equivalent to fn1

pub fn fn3<Fut1:std::marker::Send+std::future::Future+'static,Fut2>(
    a:impl Fn() ->Fut1,
    f: impl Fn() -> Fut2) 
    where 
    Fut2:std::future::Future,
 {}

Then I think why do I even need a where clause when I can tell the params exactly what to return

pub fn fn4<Fut2>(
    a:impl Fn() ->std::marker::Send+std::future::Future+'static,
    f: impl Fn() -> Fut2) 
    where 
    Fut2:std::future::Future,
 {}

But then it complains somewhere

^^^^^^^^^^^ `(dyn std::marker::Send + 'static)` is not a future

Seems like fn4 1st param is returning an unsized something. Now I see why you'd use

where Fut1:std::future::Future

But I just want an explanation. All of the errors make sense but I cannot tie all the concepts these problems represent coherently. Please leave links too.

Why the compiler requires this and cannot figure this out from the examples above.

pub fn fn5<Fut1,Fut2>(
    a:impl Fn() ->Fut1+'static+std::marker::Send,
    f: impl Fn() -> Fut2) 
    where 
    Fut1:std::future::Future+'static+std::marker::Send,
    Fut2:std::future::Future,
 {}

Param a in fn5 is actually passed as a param to another function with this signature

pub fn fn6<Fut:std::future::Future+'static>(
    path: &str, 
    process_stream:impl Fn() -> Fut
) {

process_stream returns an unsized something with no where clause but the compiler doesn't complain like fn3 and fn4. Question mark.

Let's get rid of your argument position impl Trait and write out the two functions with more explicit generics:

pub fn fn1<Fn1, Fn2, Fut1, Fut2>(a: Fn1, f: Fn2)
where
    Fn1: Fn() -> Fut1,
    Fn2: Fn() -> Fut2,
    Fut1: std::future::Future + 'static + std::marker::Send,
    Fut2: std::future::Future,
{
    // fn2(a, f)  // error[E0277]: `Fn1` cannot be sent between threads safely
}

pub fn fn2<Fn1, Fn2, Fut1, Fut2>(a: Fn1, f: Fn2)
where
    Fn1: Fn() -> Fut1 + 'static + std::marker::Send,
    Fn2: Fn() -> Fut2,
    
    Fut1: std::future::Future + 'static + std::marker::Send,
    Fut2: std::future::Future,
{
}

Now I think it's more clear here to see why the two are different: In the second one you're applying more bounds to the function, Fn1, and not to the future that it returns.


Looking at:

    a:impl Fn() ->std::marker::Send+std::future::Future+'static,
    // `(dyn std::marker::Send + 'static)` is not a future

Send and Future are traits, not types. This is a trait (and lifetime) bound:

// Type on the left must satisfy the bounds on the right
// (Be valid for `'static` and implement the given traits)
Fut1: std::marker::Send + std::future::Future + 'static

But functions return types, not traits. The compiler looked at what you wrote in the context of "I need to figure out the type here". And it turns out, you used to be able to just write Trait for what today we call dyn Trait -- a concrete (statically known) type that dynamically dispatches the methods of the trait. That's why it gave you some error about dyn Send.

Or in short, writing bare trait bounds where a type is expected does not introduce a generic type with bounds.

What you were trying to do was something like this I suppose:

pub fn fn4(
    a: impl Fn() -> (impl std::marker::Send + std::future::Future + 'static),
    f: impl Fn() -> impl std::future::Future,
) {
}

But impl Trait is not valid in that position.


In summary I suggest not using argument position impl Trait, especially when things start to get confusing like these examples. Things can be much more clear when you give the generics names, and then you can attach your bounds to the names.

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.