5

I almost have an intuitive sense of why this code should NOT work, but I can't quite put my finger on it. I think it has something to do with the fact that the new function would have a different return type every time.

Why is that a problem? Why does the direct creation work?

struct Struct<T>
where
    T: Fn(&[u8]),
{
    func: T,
}

impl<T> Struct<T>
where
    T: Fn(&[u8]),
{
    fn new() -> Struct<T> {
        // this doesn't work
        Struct { func: |msg| {} }
    }
}

fn main() {
    // this works
    let s = Struct { func: |msg| {} };
}

The error is

error[E0308]: mismatched types
  --> src/main.rs:14:24
   |
14 |         Struct { func: |msg| {} }
   |                        ^^^^^^^^ expected type parameter, found closure
   |
   = note: expected type `T`
              found type `[closure@src/main.rs:14:24: 14:32]`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
anderspitman
  • 9,230
  • 10
  • 40
  • 61
  • > "I think it has something to do with the fact that the new function would have a different return type every time" Pretty close! The question to ask is "who decides what type `T` is?" You want the implementation of `new` to decide what closure to return, but generics let the *caller* decide what type `T` is. I'm pretty sure there's another question that addresses this... I'll look for it. – trent Jan 06 '19 at 01:27
  • Ok I think that makes sense. Basically if the new function worked the type would be deciding for itself what T is, rather than the calling code deciding? – anderspitman Jan 06 '19 at 01:30
  • Yep! But that's also a useful thing to do sometimes, and you can do it with `impl Trait`. [What is the correct way to return an Iterator (or any other trait)?](https://stackoverflow.com/q/27535289/3650362) is the other question I was looking for. Just change `fn new() -> Struct` to `fn new() -> Struct` and it will work. – trent Jan 06 '19 at 01:35
  • Ok it's close. However, after your change I still can't actually call the new function. It gives "type annotations required: cannot resolve `for<'r> <_ as std::ops::FnOnce<(&'r [u8],)>>::Output == ()`" – anderspitman Jan 06 '19 at 02:03
  • The other answer has a lot of info but I'm not 100% sure it applies here. I'm specifically trying to set a type from within another type, not trying to return one. Although it's all tied together... – anderspitman Jan 06 '19 at 02:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186248/discussion-between-trentcl-and-anderspitman). – trent Jan 06 '19 at 02:07

1 Answers1

5

tl;dr; You can do the following:

fn new() -> Struct<impl Fn(&[u8])> {
    Struct { func: |msg| {} }
}

More detailed:

Let's dissect your impl block:

impl<T> Struct<T>
where
    T: Fn(&[u8]),
{
    fn new() -> Struct<T> {
        // this doesn't work
        Struct { func: |msg| {} }
    }
}

We start with:

impl<T> Struct<T>
where
    T: Fn(&[u8]),

This tells the compiler that the whole impl block is "valid" for any T satisfying Fn(&[u8]).

Now:

fn new() -> Struct<T> {
    // this doesn't work
    Struct { func: |msg| {} }
}

You say that new returns a Struct<T>, and we are within the block stating that everything inside it works for any T satisfying Fn(&[u8]). However, you return one particular instance of Struct, namely the one parametrized by |msg| {} - thus, the return value can not be a Struct<T> for any T satisfying Fn(&[u8]).

However, you can modify it to do the following:

fn new() -> Struct<impl Fn(&[u8])> {
    Struct { func: |msg| {} }
}

This tells the compiler that new returns a Struct, whose parameter is known to satisfy Fn(&[u8]), so that the compiler should infer it. In particular it has no assumptions about T and returns one particular type.

In the direct initialization, however, we tell the compiler:

let s = Struct { func: |msg| {} };

The compiler sees that you want to create a Struct and knows that - in order to create it - it must infer the type for T res. func. It sees that you passed |msg| {} for func, infers the type for the closure and now knows a concrete type to put into the T.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • Thanks for the great explanation. That all makes sense on the surface, however when I tried to use this code I see that your changes allow the code to compile but will not allow me to use `Struct::new()` with the default closure. When I try, I get an error https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=195e4f2be90803d86a83d4b12a6c66a3. The error is `cannot satisfy for<'r> <_ as FnOnce<(&'r [u8],)>>::Output == ()` – Schneems Jul 18 '22 at 19:26