self_cell icon indicating copy to clipboard operation
self_cell copied to clipboard

Support async builders?

Open fzyzcjy opened this issue 1 year ago • 9 comments
trafficstars

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.

fzyzcjy avatar Jun 15 '24 02:06 fzyzcjy

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?

Voultapher avatar Jun 15 '24 06:06 Voultapher

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).

fzyzcjy avatar Jun 15 '24 06:06 fzyzcjy

Could you please outline specifically what kind of API you would need.

Voultapher avatar Jun 15 '24 07:06 Voultapher

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

fzyzcjy avatar Jun 15 '24 07:06 fzyzcjy

It would need to async fn new( right?

Voultapher avatar Jun 15 '24 08:06 Voultapher

Also I wonder if a minimal solution that only duplicates the new constructor is enough for a first version until people ask for more.

Voultapher avatar Jun 15 '24 08:06 Voultapher

@steffahn what's you take, do we run into any variance/soundness issues with such an API?

Voultapher avatar Jun 15 '24 08:06 Voultapher

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!

fzyzcjy avatar Jun 15 '24 08:06 fzyzcjy

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.

steffahn avatar Jun 15 '24 10:06 steffahn

If there are other users that request this, I will take another look.

Voultapher avatar Sep 06 '24 15:09 Voultapher

@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 avatar Jan 17 '25 07:01 tliron

@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.

Voultapher avatar Jan 17 '25 10:01 Voultapher

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?

tliron avatar Jan 17 '25 21:01 tliron

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.

Voultapher avatar Jan 18 '25 10:01 Voultapher

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.

tliron avatar Jan 18 '25 17:01 tliron

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.

Daniel-Aaron-Bloom avatar Apr 01 '25 19:04 Daniel-Aaron-Bloom

I wonder if the new async closure support added to Rust 1.85 can make this easier for us to implement.

tliron avatar Apr 01 '25 19:04 tliron

It will likely require duplicating quite some code, I have an idea how to avoid that. I'll look into it.

Voultapher avatar Apr 08 '25 10:04 Voultapher

@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" }.

Voultapher avatar Apr 08 '25 17:04 Voultapher

I was starting to work on it, too, and took a different approach. :) But I like yours, @Voultapher . Will test!

tliron avatar Apr 08 '25 18:04 tliron

@Voultapher It works. :) You can see my code here, I think your solution is quite intuitive!

tliron avatar Apr 08 '25 18:04 tliron

Just out of curiosity, what approach did you pursue?

Voultapher avatar Apr 09 '25 06:04 Voultapher

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)?

tliron avatar Apr 09 '25 15:04 tliron

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_on the async function and get sync behavior.

Voultapher avatar Apr 11 '25 11:04 Voultapher

FYI I've published version 1.2.0 which includes the feature.

Voultapher avatar Apr 11 '25 11:04 Voultapher