self_cell
self_cell copied to clipboard
Support async builders?
Hi thanks for the library! I do hope the builder function can support async functions.
Context: I am considering using this package to implement returning borrowed types in https://github.com/fzyzcjy/flutter_rust_bridge. Thus the builder can be arbitrary async code.
Hi, thanks for reaching out. Do I understand correctly, you have a dependent construction logic that involves async calls, right?
If possible, I'd like to avoid the async coloring issue of having multiple variants of all construction functions. There are also some question about variance across await points, where I'm not sure what a sound API would look like.
Would a spawn_blocking work for your use-case?
Hi, thanks for the reply! Ok I see...
Would a spawn_blocking work for your use-case?
Well no, because https://github.com/fzyzcjy/flutter_rust_bridge needs to support arbitrary user function. If we require users to do spawn_blocking, then indeed we lose the benefits of async (e.g. not taking a whole thread).
Could you please outline specifically what kind of API you would need.
Sure. For example, the README example says:
impl NewStructName {
fn new(
owner: Owner,
dependent_builder: impl for<'a> FnOnce(&'a Owner) -> Dependent<'a>
) -> NewStructName { ... }
but now it would be great to be
impl NewStructName {
fn new(
owner: Owner,
dependent_builder: impl for<'a> FnOnce(&'a Owner) -> Pin<Box<dyn Future<Output = Dependent<'a>>>> // <- support passing in async function. not necessarily have API like this.
) -> NewStructName { ... }
Indeed just something like https://docs.rs/ouroboros_examples/latest/ouroboros_examples/ouroboros_impl_chain/struct.Chain.html#method.new_async
It would need to async fn new( right?
Also I wonder if a minimal solution that only duplicates the new constructor is enough for a first version until people ask for more.
@steffahn what's you take, do we run into any variance/soundness issues with such an API?
It would need to async fn new( right?
Oh yes!
Also I wonder if a minimal solution that only duplicates the new constructor is enough for a first version until people ask for more.
Sorry not quite get it...
Btw, no worries about this issue, because if https://github.com/Voultapher/self_cell/issues/58 cannot be solved, then I unfortunately cannot use this package :( Though I think it is great and abstracts out unsafe things with well tested code!
IIRC ouroboros has async construction APIs for a while already, so if we'd just mirror those, it might be sound (well.. or both are unsound). I haven't looked at the details myself yet.
If there are other users that request this, I will take another look.
@Voultapher, astounding library, thank you for it!
I hate to comment on a closed issue, but it seems you closed this one as "completed". Is there really async support now, or did you mean to close it as "won't implement"?
As it stands, I am using both self_cell and Ouroboros in my project, the former for blocking pathways and the latter just for async. I would honestly prefer self_cell for both. I've tried pretty hard to get self_cell to work with Pin::box(async move) closures, as in Ouroboros, but reached the limits of my understanding. :) If there is a way to do it without changing self_cell, just including an example would be enough to "close as completed", I reckon.
Is this a feature you'd rather not have in self_cell at all, or would you be willing to accept help to develop it?
@tliron realistically the draining part is maintaining the feature and documentation and looking out for soundness bugs, not really the developing part. I might take another stab at this at some point in the future , but for now it's not planned.
I understand and sympathize.
In your opinion, is it possible to achieve with self_cell's design as it stands, without modifications? In that case, it would be enough to include an example. I think that would involve no further maintenance beyond the inclusion of it.
If you think it's possible I might pursue trying to create such an example, but I'd rather not even try if you think it's plain impossible. What is your opinion?
I don't think there is an elegant way to do it without modifying the source code, something like futures::block_on could be used but won't compose nicely.
Could you please provide your specific use-case so that I can check that if I would add this feature that it would even solve our use-case.
We might be thinking of a different use case, because futures::block_on cannot work in an async situation. I think you are thinking about bridging async code to blocking.
My exact situation is development of the read-url crate. Specifically for opening zip: URLs, I need to "carry" the zip crate's data with the reader. For the blocking variant, self_cell is terrific! Here is that code. As you can see, there are "pairs" of dependents due to the architecture of the crate I am using, but self_cell expresses these pairs very nicely. For the async variant, I had no choice but to use Ouroboros. Here is the async code. As you can see, Ouroboros lets me use async closures (with Pin<Box>) to build the dependents with a constructor function that is also async, allowing the entire flow to be async.
Here are some of my sad attempts to try to do the same in self_cell. :) I stopped working on it because I thought to check here, first.
Just putting a +1 on this feature request. My use case is trying to store an Arc<PubsubClient> and an associated Stream from one of the functions.
I wonder if the new async closure support added to Rust 1.85 can make this easier for us to implement.
It will likely require duplicating quite some code, I have an idea how to avoid that. I'll look into it.
@Daniel-Aaron-Bloom @tliron I've created a PR that adds support for async builders. Could you please try the feature and tell me if it solves your use-cases self_cell = { git = "https://github.com/Voultapher/self_cell", branch = "add-async-builder-support" }.
I was starting to work on it, too, and took a different approach. :) But I like yours, @Voultapher . Will test!
@Voultapher It works. :) You can see my code here, I think your solution is quite intuitive!
Just out of curiosity, what approach did you pursue?
I was wrapping the dependents with BoxFuture and introduced ::new_async as an addition to ::new.
Actually, I wonder if we can borrow an idea from my stalled approach for your design. Right now you use async_builder to alter the macro output, but would it really be so bad if the macro always outputted both ::new and ::new_async? (and ::try_new and ::try_new_async)?
That was a deliberate decision, for the following reasons:
-
The amount of cases where both async and sync construction are required for the same type are far and few between.
-
The crate focuses on a simple to use and minimal interface. For example ouroboros allows for multiple owners and dependents in the same struct, self_cell does not. That's a deliberate decision to keep the documentation, concepts and implementation lean. Same reasoning goes into only having one set of full sync or full async construction functions.
-
async is strictly more powerful than sync. Worst case users can relatively cheaply
block_onthe async function and get sync behavior.
FYI I've published version 1.2.0 which includes the feature.