Home > Enterprise >  How to refer to the type of a closure in a generic return value?
How to refer to the type of a closure in a generic return value?

Time:06-09

I have a struct that contains a function, let's say of type F: Fn() -> String. And I want to initialize it in its constructor to a particular default function, which can later be overridden by the caller if they like.

struct Builder<F> {
    func: F,
}

fn new_builder() -> ??? {
    let hello = "hello".to_owned();
    Builder { func: move || hello }
}

impl<F: Fn() -> String> Builder<F> {
    fn build(self) -> String {
        (self.func)()
    }
}

But how do I write the return type of new_builder(), since || {} has an anonymous type? I tried with -> Builder<_> but that fails (playground), and suggests a solution that's not even syntactically valid:

error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types
 --> src/lib.rs:5:29
  |
5 | fn new_builder() -> Builder<_> {
  |                     --------^-
  |                     |       |
  |                     |       not allowed in type signatures
  |                     help: replace with the correct return type: `Builder<[closure@src/lib.rs:6:21: 6:26]>`

For more information about this error, try `rustc --explain E0121`.

I know I can solve it by making Builder non-generic with some boxing:

struct Builder {
    func: Box<dyn Fn() -> ()>,
}

But that incurs additional runtime overhead in the form of allocations and indirections, and might make it harder for the compiler to optimize, e.g. inline calls to func. This workaround is plenty fast in my practical situation, but I'm still wondering if Rust lets me stick to a purely static solution here.

Is there a way to make this work without resorting to references or boxing?

CodePudding user response:

The main problem here is that the type of a closure cannot be written out in code.

Luckily, for exactly that reason, the impl keyword exists for return types:

struct Builder<F> {
    func: F,
}

fn new_builder() -> Builder<impl FnOnce() -> String> {
    let hello = "hello".to_owned();
    Builder {
        func: move || hello,
    }
}

impl<F: FnOnce() -> String> Builder<F> {
    fn build(self) -> String {
        (self.func)()
    }
}

fn main() {
    let builder = new_builder();
    println!("{}", builder.build());
}

Which means: "This is some unspecified type that the compiler will figure out. It's just important that this type implements FnOnce() -> String".

Other functions that use new_builder will then be able to access func as an FnOnce() -> String object.

Also note that I had to change the type to FnOnce because func in your case consumes the hello and is therefore only callable once.

Don't worry, though. impl<F: FnOnce() -> String> also includes Fn and FnMut, as they are also FnOnce implicitely.

  • Related