Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upFusedFuture and FusedStream #1219
Comments
This comment has been minimized.
This comment has been minimized.
|
To clarify: The proposed |
This comment has been minimized.
This comment has been minimized.
cavedweller
commented
Aug 21, 2018
|
I'm leaning toward the |
This comment has been minimized.
This comment has been minimized.
|
If it’s going to be on the main I think I’m on the side of extra traits for this, I have some futures that don’t currently keep track of when they’re done and basically just restart if polled again after being ready, tracking this extra state could get annoying. Would |
This comment has been minimized.
This comment has been minimized.
I had imagined it as something more like
To clarify: you're referring to the additional method on
Yeah, I thought about that, but I'm concerned that there might be a potential performance regression due to the extra branch everywhere, and there's a lot of code that uses |
This comment has been minimized.
This comment has been minimized.
|
About single About About integrating it into the I think I lean towards the manual fuse technique: pin_mut!(future_1, future_2, my_stream);
fuse!(future_1, future_2, my_stream); // Returns `future::Fused`s with `has_completed` methods
loop {
select! { ...It's simple because it doesn't require special traits and manual future impl also don't have to care about it. Unless there's a performance reason, I think that is the best course of action. |
This comment has been minimized.
This comment has been minimized.
|
@MajorBreakfast can you clarify what you mean by "manual fuse technique"? Do you mean requiring the types to be wrapped in a literal WRT |
This comment has been minimized.
This comment has been minimized.
|
That select loop looks to me that it's a join, right? Join already keeps track of which futures have finished. If there's subtle differences, it seems like this seemingly niche case could make use of the existing
|
This comment has been minimized.
This comment has been minimized.
@cramertj Yes. That solution is certainly the simplest option and I doubt that the impact on performance will ever be measurable. Yes, the state is technically duplicated, but who cares if it doesn't have an impact on performance. It might even be faster if these flags were stored in sequence as an array due to cache locality. A special |
This comment has been minimized.
This comment has been minimized.
No, it's not the same as join-- there needs to be a separate state transformation once each future has completed or stream element has been received, often including spawning new tasks to run. This is super common in Fuchsia code right now that uses state-machine style
Yeah, the
+1 |
This comment has been minimized.
This comment has been minimized.
I proposed that at the start of this conversation, but I'm opposed to that due to the limitations it places on control flow. |
This comment has been minimized.
This comment has been minimized.
|
Does anyone have any more thoughts on this? I'm still leaning in the direction of |
This comment has been minimized.
This comment has been minimized.
|
Here are some more random thoughts: In order for this trait to be meaningful, every time we write this: type MyFuture<'a> = impl Future<Output = T> + 'a; // Notation from RFC 2515
fn foo() -> MyFuture<'a> {
async { ... }
}we would need to write this instead: type MyFuture<'a> = impl Future<Output = T> + FusedFuture + 'a;
fn foo() -> MyFuture<'a> {
async { ... }
}otherwise we "forget" about the "fusedness" even though every type returned by an async fn implements it. trait Computer {
type Boot<'a>: Future<Output = ()> + 'a; // Should `FusedFuture` be listed here?
fn boot(&mut self) -> Self::Boot<'_>;
}
impl Computer for MacBook {
type Boot<'a> = impl Future<Output = ()> + 'a; // And here?
fn boot(&mut self) -> Self::Boot<'_> { async { ... } }
}What I'm saying is that having to mention this trait everywhere will become tedious and omitting it means that we're back to the status quo. (Note: I hope that I didn't become confused and wrote something syntactically incorrect. @cramertj feel free to edit this post) Also, we need to come up with a different name that fits. A "fuse" in electrical engineering means protection against high currents. For |
This comment has been minimized.
This comment has been minimized.
smklein
commented
Aug 25, 2018
|
@MajorBreakfast Naming nitpick: Do we expect the direction of Futures to evolve in a direction that they may have additional states other than "Complete" and "Incomplete"? "StatefulFuture" may be an overloaded term compared to something more boolean. All Futures are state machines (outside of this particular context around completion); I'm hopeful that we can convey a name that encapsulates "can this future be queried for completion". As @cramertj pointed out, many Futures are already aware of this context without attaching additional state. The real distinction is that proposed CheckableFuture? QueryableFuture? CompletionFuture? SelfAwareFuture? (Not really confident these are better names, just throwing out some ideas...) |
This comment has been minimized.
This comment has been minimized.
|
I'm -1 to any name that doesn't involve As to adding |
This comment has been minimized.
This comment has been minimized.
tkilbourn
commented
Aug 27, 2018
|
Is there a way to position the design such that we could use a default implementation on |
This comment has been minimized.
This comment has been minimized.
|
@tkilbourn You could imagine something like a default method A close approximation to this approach is the |
cramertj commentedAug 20, 2018
The
selectmacro would be far more useful if it was usable inside of loops, like this:However, the
StreamandFuturetraits require that they are not polled after completion, and they provide no way to check if completion has already occurred. It's possible to track this extra bit of state in a boolean or something as part of the macro, but then the looping functionality has to be built into the macro, meaning that (1) there would have to be separateselect!andselect_loop!macros and (2) any shared code or controlflow in the "always" branch would have to be duplicated across branches:Another option would be to add
FusedFutureandFusedStreamwithhas_completed()methods, implementFusedFutureandFusedStreamfor many of the combinators which support them (in particular thefuse()combinator). This is likely more ergonomic for users familiar with how the system works, but could be confusing for new users who now have one more thing to understand (fusing, pinning, etc...).Yet another option would be to require that all futures track this bit of state and add a
has_completedmethod to the Future and Stream traits. This requires less involvement from end-users, but again increases the already-high ergonomic burden on manual-futures implementors.Personally, I lean towards the
FusedFuture/FusedStreamapproach as it adds minimal complexity for users who don't care about theselect!combinator while still retaining the manual control-flow flexibility ofselect!+looprather thanselect_loop!. However, I'm by no means attached to this idea, and I recognize that it pushes another point of complexity onto end users, which is disappointing. I'd like to hear what y'all think.cc @seanmonstar @carllerche @withoutboats @aturon @Nemo157 @MajorBreakfast @cavedweller @tkilbourn @smklein