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.