The type inference for `T` cannot be done when the matched type has the form of `T::Output` and the trait is user-defined but it can be done for `FnTrait`

trait MyTrait<T> {
    type Output;
}
impl<T> MyTrait<T> for T {
    type Output = T;
}
fn show<T: MyTrait<T, Output = T> + Default>() -> T::Output {
    T::default()
}

fn main() {
    let i: i32 = show();
}

In this example, even thought I have implies that T::Output is T in the trait bound T: MyTrait<T, Output = T>, however, the compiler cannot infer type for T. If I change the return type to T, the compiler can infer T to i32. In contrast, the compiler can infer FnTrait::Output, for example:

fn infer_output<F,U:Default>(_:F)->F::Output
where F:Fn()->U
{
   U::default()
}
fn function<U:Default>()->U{
    U::default()
}
fn main() {
    let i:i32 = infer_output(function);
}

Similar to the first case, the trait bound F:Fn()->U implies that F::Output is U, and the compiler can infer U to i32 as what is expected.

I think the compiler should enhance the type inference for the first example to make the type inference behave the same as that of the second case.

I never knew there were cases where you could directly refer to the Output type of an FnOnce without #![feature(unboxed_closures)]!

This code does not need to use #![feature(unboxed_closures)]

I know, that’s what I was surprised about :wink:

The examples are different. I don't think it's that Fn is special here, I think it's that you have a different set of generic types, implementations on said types, and a different calling pattern. One or more of those accounts for the difference.

In the Fn version, you're passing in function, and the compiler has something like

let i: i32 = infer_output::<typeof(function)::<U?>, U?>(function);

and can constrain typeof(function)::<U?>: Fn<(), Output = i32> from the signature and the i32 annotation, and then use the notional implementation of FnOnce/Fn to figure out U? = i32.

If I adjust your non-Fn example to match, it similarly succeeds.

If you remove the argument (so that T isn't an input), it fails. However, I had to make other changes to align the two examples, so maybe the thing you care about is part of those changes.


Let me assume the Fn stuff was just a distraction / misunderstanding and go back to your non-compiling code. There you have something like

let i: i32 = show::<T?>();

And the compiler can[1] constrain

T?: MyTrait<T?, Output = i32>

but apparently can't "work backward" from the Output = T? portion of the original bound to infer T = i32. I think that's what your feature request is really about. [2]

(In contrast, if you change the return type to T instead of T::Output, then T? = i32 can be applied directly and works today).


I'd be sort of surprised if there's not an existing issue about this, but I didn't find one offhand. You could create one.


  1. hypothetically, i.e., I don't know that it bothers to get this far when T? isn't an input at the call site ↩︎

  2. Mostly guessing, but perhaps it could be summed up as "consider projections to be input parameters when an associated type bound makes them equal to an input parameter" or "eagerly replace projections with parameters given an equality bound" or something like that. ↩︎

1 Like

I posted the issue to rust-lang.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.