-
Notifications
You must be signed in to change notification settings - Fork 118
Description
1a) So I understand that I can do:
let lazy: Lazy<String> = Lazy::new(|| String::from("hello"));
Because:
- When you don't specify the 2nd generic it assumes
fn() -> String
- This closure coerces to
fn() -> String
1b) I also understand that I can do:
let data = String::from("hello");
let lazy: Lazy<String, _> = Lazy::new(move || data);
Because:
- This closure doesn't coerce to
fn() -> String
, since it captures something - However, if I use
_
I can let the type system infer the type of the closure itself (closures are a Voldemort type, one cannot name their actual type in code so I couldn't replace_
with the true type even if I tried).
2a) Now what if I wanted to put the Lazy into a struct?
struct Foo(Lazy<String>);
let foo = Foo(Lazy::new(|| String::from("hello")));
Works fine
2b) But how would I do this in the case where my closure captures something?
struct Foo(Lazy<String>);
let data = String::from("hello");
let foo = Foo(Lazy::new(move || data)); // Compile error: closure doesn't coerce to `fn() -> String`
_
doesn't work. Am I forced to do something like this?
struct Foo<F: FnOnce() -> String>(Lazy<String, F>);
let data = String::from("hello");
let foo = Foo(Lazy::new(move || data));
This isn't always convenient.
Start Edit A much simpler example than what I write below is something like
struct Foo<F: FnOnce() -> String>(Lazy<String, F>);
let data = String::from("hello");
let foos = vec![
Foo(Lazy::new({ let data_clone = data.clone(); move || data_clone + "1" })),
Foo(Lazy::new({ let data_clone = data.clone(); move || data_clone + "2" })),
];
This doesn't compile because I want the two Foo<F>
s to be the same type in order to put them in the vec, but they use different closures so they are not the same type.
End Edit
For example, if Foo is recursive in any way (e.g. a lazy-tree, which is how I came across this):
struct Foo {
value: i32,
left: Option<Box<Lazy<Foo>>>,
right: Option<Box<Lazy<Foo>>>,
}
let data = 17;
let foo = Foo {
value: data,
left: Some(Box::new(Lazy::new(move || Foo { value: data * 2, left: None, right: None }))),
right: Some(Box::new(Lazy::new(move || Foo { value: data *2 + 1, left: None, right: None }))),
};
There's no way I can add the function generic to Foo
any more: struct Foo<F: FnOnce() -> Foo<F>>
. This is recursive, which is apparently fine on its own but it causes problems because once F
is resolved it can only match a single closure. This means that all of the Lazy
s all the way down need to use the same closure - which I obviously don't want.
So, am I forced to use Lazy<Foo, Box<dyn FnOnce() -> Foo>>
and box up all my closures? This is what I've got working but is there no nicer way?
struct Foo {
value: i32,
left: Option<Box<Lazy<Foo, Box<dyn FnOnce() -> Foo>>>>,
right: Option<Box<Lazy<Foo, Box<dyn FnOnce() -> Foo>>>>,
}
let data = 17;
let foo = Foo {
value: data,
left: Some(Box::new(Lazy::new(
Box::new(move || Foo { value: data * 2, left: None, right: None }) as Box<dyn FnOnce() -> Foo>
))),
right: Some(Box::new(Lazy::new(
Box::new(move || Foo { value: data * 2 + 1, left: None, right: None }) as Box<dyn FnOnce() -> Foo>
))),
};