My requirement is to make a wrapper over an inner AsyncWrite (lets call it MyWriter) which would provide the following features:
Have cheap cloning (reusing underlying AsyncWrite to push new bytes anytime by any of the multiple owners of the reference)
Have interior mutability (because I loop over the owners which are basically independent futures that eventually resolve and write bytes into their copy of MyWriter)
This is the naive version of putting the Pin needed by AsyncWrite, and Box needed to Deref inside the classic Rc<RefCell<..>>> combo
pub struct MyWriter<W> {
inner_writer: Rc<RefCell<Pin<Box<W>>>>
}
impl<W> MyWriter<W> where W: AsyncWrite {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
let mut writer_b: std::cell::RefMut<'_, Pin<Box<W>>> = self.inner_writer.borrow_mut();
let writer_pinned: Pin<&mut W> = writer_b.deref_mut().as_mut();
writer_pinned.poll_write(cx, buf)
}
// And similar impl of poll_flush & poll_close
}
It seems to work, but my main concern is the interaction with Pin because I'm not sure whether I'm doing something wrong by having it stored inside a RefCell. Also the impl of AsyncWrite methods feels like bruteforcing a Pin<&mut W> out of this structure just to be able to use its own AsyncWrite methods. So, is there a better alternative?
I found PinCell crate which allows pin-safe version of interior mutability, to be able to use projections on a Pin<PinCell<W>>, but I couldn't figure out the cloning part, because cloning the above leads to a deep copy of W, and putting an Rc on top (like Rc<Pin<PinCell<W>>>) doesn't allow to obtain the owned Pin<&mut W> needed to call the inner W's AsyncWrite methods
Edit: It's a single-threaded (Sync + !Send) environment, hence no Arc or Mutex
Secondly, I wouldn't be able to use AsyncWrite for MyWriter, because its impl requires self: Pin<&mut Self> on its methods - where this approach would rather use the immutable self: Pin<&Self> to benefit from internal mutability & copying the structure around - I guess it's OK to implement everything in a similar way AsyncWrite does it, just a little inconvenient
yes it is possible. that's actually the only thing you can do.
that's what PinMut<'a, T> is for.
AsyncWrite requires unique access(with a Pin<&mut W> during its use, so you must provided.
this can be done with Pin<Rc<PinCell<W>>> because that's what they are used for (as long as you don't try to have it locked in 2 places at the sme time of course)
Really, how ? Projection gives Pin<&Rc<PinCell<W>>>, and then I went through all available methods - as_ref() results in identity, get_ref() and deref() both remove Pin
Yes of course it's possible, but my point is that if we have the struct
then the impl of AsyncWrite on MyWriter is pointless, because we would need a Pin<&mut Self> in the argument, so MyWriter would need to be a mutable to be used in the first place, defeating the point
I'm not really seeing how to use PinMut as sole mutable property here without the parent not being mutable
well thats not the same thing from what you asked at all. actually that's completely different.
you asked how to go from Pin<Rc<Self>> to Pin<&Self>. this is trivially easy and basically the only things you can do.
now you ask how to go from Pin<&Rc<PinCell<W>>> to Pin<&PinCell<W>>.
this is not allowed. this is unsound, and breaks the Pin guarantee.
what would allow you to do this is not projection, it's something else, wich i have done some research on a while back, and afaik is not available anywhere.
you could choose to implement AsyncWrite on &MyWriter<W>. and even if you impl it on MyWriter<W>, you can still clone() them.
i expect you will probably get runtime pnics due to double locking though.
I don't think that crate is needed in your case. I also don't see what's wrong with using Rc<RefCell<Pin<Box<W>>>>. PinCell solves pin projection through a cell (Pin<&PinCell<T>> -> Pin<&mut T>). You don't need that, your Pin is inside the cell protecting W, not outside trying to project through.
Also why not just do this? It's safe to use get_mut() in this case, because your MyWriter just contains a pointer/Rc :
About the runtime locking, I also see your approach - basically lock each instance from the getgo, and yes that would be problematic. Ideally each poll, when Ready, gets the lock (borrow_mut), writes the bytes, and releases it. The poll signature however cannot be Pin<&mut Self>, in fact it's probably best to implement custom polling with just &MyStruct as argument, without AsyncWrite impl. I think cloning self.inner_writer as &Pin<Rc<PinCell>> should be fine
Box<W> is alwaysUnpin regardless of W, so I think OP's solution is quite elegant to be honest. If the double indirection is ever an actual issue, you would need unsafe code or a different design, because PinCell would make cloning more problematic compared to current approach.
what do you mean by that ? the PinCell approach handles cloning the same as the Box approach. it's all handled by the Rc.
the PinCell approach (Pin<Rc<PinCell<W>>>) actually does the exact same thing as the Box aproach (Rc<RefCell<Pin<Box<W>>>>) except ith slightly different methods chains
You are right, I checked the source, PinCell is just a wrapper over RefCell, which is also unconditionally Unpin in std [Edit: This is wrong. See below.]. I thought cloning Pin<Rc<PinCell<T>>> would require T: Unpin, but since RefCell<T>: Unpin regardles of T, so is PinCell<T>, so cloning works without extra bounds.
But now we can just simplify OP's approach to this, much cleaner, single allocations. The only caveat is the Unpin bound, this shouldn't be an issue, because most async writers are Unpin anyway. PinCell would be necessary if they need an explicit workaround to not have that Unpin bound + single allocation (this should be very rare in practice)
pub struct MyWriter<W> {
inner_writer: Rc<RefCell<W>>,
}
impl<W> Clone for MyWriter<W> {
fn clone(&self) -> Self {
Self {
inner_writer: Rc::clone(&self.inner_writer),
}
}
}
impl<W: AsyncWrite + Unpin> AsyncWrite for MyWriter<W> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
// Short version:
// Pin::new(&mut *self.inner_writer.borrow_mut()).poll_write(cx, buf)
// Or broken down:
let mut guard: RefMut<'_, W> = self.inner_writer.borrow_mut();
let writer: &mut W = &mut *guard;
let pinned: Pin<&mut W> = Pin::new(writer);
pinned.poll_write(cx, buf)
}
// similar for poll_flush, poll_close
}
EDIT: The code I ran made it look like RefCell was Unpin unconditionally (that's what you would think because PinCell was just a wrapper over RefCell). But after going deeper into this, it's clear that RefCell is not unconditionally Unpin. Pin derives Clone directly, so Pin<Rc<T>> is Clone whenever Rc<T> is Clone (which is always, regardless of T). There's a note about Clone being unsound in case of mutable references. We're using Pin<Rc<...>>, and Rc::clone just bumps the refcount, so it doesn't apply here.