Instantiate type with closure type parameter?

I have the following code:

struct MakeVec<F: FnOnce(usize) -> Vec<u8>> {
    f: F,
}

impl<F: FnOnce(usize) -> Vec<u8>> MakeVec<F> {
    fn new(f: F) -> MakeVec<F> {
        MakeVec { f }
    }

    fn new_std() -> MakeVec<impl FnOnce(usize) -> Vec<u8>> {
        MakeVec { f: |n| vec![0; n] }
    }
}

I want new to take a function supplied by the user, while new_std uses the vec! macro from the standard library in a closure. The client shouldn't need to know the concrete type of the closure, only that it impl's the right trait. This code compiles.

However, when you try to call the function, the compiler tries to infer the type of F, and fails:

error[E0284]: type annotations required: cannot resolve `<_ as std::ops::FnOnce<(usize,)>>::Output == std::vec::Vec<u8>`
  --> src/main.rs:16:13
   |
16 |     let _ = MakeVec::new_std();
   |             ^^^^^^^^^^^^^^^^
   |
note: required by `<MakeVec<F>>::new_std`
  --> src/main.rs:10:5
   |
10 |     fn new_std() -> MakeVec<impl FnOnce(usize) -> Vec<u8>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Is there a way to make this work? Putting new_std in its own impl block doesn't seem to work since it would require writing down the type of the closure, which you can't do.

NOTE: I know you can do this with a bare function, but I'd prefer it to be an associated constructor, as that's the idiomatic way to do it.

You can put that method in a non-generic block like:

impl MakeVec<fn(usize) -> Vec<u8>> {
    fn new_std() -> ...
}

This way it's not depending on an F that it doesn't need.

1 Like

That certainly works, but it's not exactly what I'm looking for - fn(usize) -> Vec<u8> is a function pointer which needs to be called at runtime, as opposed to a particular function, which can be inlined at compile-time. This is in a hot loop, so I'd prefer to use the monomorphized version if I can manage it.

Also, if you're willing to use unstable features, I figured out that you can do this:

struct NewVec;

impl FnOnce<(usize,)> for NewVec {
    type Output = Vec<u8>;

    extern "rust-call" fn call_once(self, (n,): (usize,)) -> Vec<u8> {
        vec![0; n]
    }
}

impl MakeVec<NewVec> {
    fn new_std() -> MakeVec<NewVec> {
        MakeVec::new(NewVec)
    }
}

The function pointer doesn't matter, because new_std() is returning its own type of MakeVec, not Self. You can prove that you're still getting a zero-sized type like this:

    println!("{}", std::mem::size_of_val(&MakeVec::new_std()));

All we need for the impl block is some concrete type that satisfies the type requirements of MakeVec. If you had left the actual struct unconstrained by FnOnce (putting that constraint elsewhere), then you could use anything at all to fill that generic slot, like impl MakeVec<()>.

1 Like

Oh that makes sense, thanks so much!