Problem with "duplicating" a (Tcp)Stream and lifetimes

Since I need into_split (split is already provided by borrowing std::net::TcpStream), I made an attempt to create such a newtype:

use std::io;
use std::net::TcpStream;
use std::os::unix::net::UnixStream;
use std::sync::Arc;

struct OwnedReadHalf<C>(Arc<C>);
struct OwnedWriteHalf<C>(Arc<C>);

fn into_split<C>(connection: C) -> (OwnedReadHalf<C>, OwnedWriteHalf<C>) {
    let arc1 = Arc::new(connection);
    let arc2 = arc1.clone();
    (OwnedReadHalf(arc1), OwnedWriteHalf(arc2))
}

impl io::Read for OwnedReadHalf<TcpStream> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        (&*self.0).read(buf)
    }
    // TODO: implement more methods
}

impl io::Read for OwnedReadHalf<UnixStream> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        (&*self.0).read(buf)
    }
    // TODO: implement more methods
}

impl io::Write for OwnedWriteHalf<TcpStream> { … }

impl io::Write for OwnedWriteHalf<UnixStream> { … }

This code compiles. However, there are multiple issues:

  • I have to forward each method of Read and Write to (&*self.0). E.g. if I do not implement std::io::Read::read_vectored, then my wrapper would use the default implementation, which may perform worse. Moreover, if Read and Write get additional methods (with default implementations) in future, the same problem will apply to those methods (and users of my code won't even notice the missing methods).
  • I have to provide implementations (for all methods) for all types of streams I want to operate on (e.g. TcpStream, UnixStream, …), resulting in boilerplate code in two dimensions (count of methods in Read and Write × count of different stream types).

I tried to further abstract the implementation to get rid of one dimension of boilerplate. My futile attempt was as follows:

impl<R> io::Read for OwnedReadHalf<R>
where
    &'_ R: io::Read,
{
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        (&*self.0).read(buf)
    }
}

But the compiler won't allow that:

error[E0637]: `'_` cannot be used here
  --> src/io_help.rs:23:6
   |
23 |     &'_ R: io::Read,
   |      ^^ `'_` is a reserved lifetime name

error[E0310]: the parameter type `R` may not live long enough
  --> src/io_help.rs:23:12
   |
21 | impl<R> io::Read for OwnedReadHalf<R>
   |      - help: consider adding an explicit lifetime bound...: `R: 'static`
22 | where
23 |     &'_ R: io::Read,
   |            ^^^^^^^^ ...so that the reference type `&'static R` does not outlive the data it points at

If I try to make R: 'static, the second error looks different:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/io_help.rs:27:11
   |
26 |     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
   |             --------- this data with an anonymous lifetime `'_`...
27 |         (&*self.0).read(buf)
   |           ^------  ---- ...and is required to live as long as `'static` here
   |            |
   |            ...is captured here...

I tried to introduce a named lifetime:

impl<'a, R> io::Read for OwnedReadHalf<R>
where
    R: 'a,
    &'a R: io::Read,
{
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        (&*self.0).read(buf)
    }
}

But that didn't make things any better:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/io_help.rs:27:11
   |
27 |         (&*self.0).read(buf)
   |           ^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 26:13...
  --> src/io_help.rs:26:13
   |
26 |     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
   |             ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/io_help.rs:27:12
   |
27 |         (&*self.0).read(buf)
   |            ^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 21:6...
  --> src/io_help.rs:21:6
   |
21 | impl<'a, R> io::Read for OwnedReadHalf<R>
   |      ^^
note: ...so that the types are compatible
  --> src/io_help.rs:27:20
   |
27 |         (&*self.0).read(buf)
   |                    ^^^^
   = note: expected `std::io::Read`
              found `std::io::Read`

So I'm kinda stuck again.

I feel like the standard library's approach of implementing Read for &TcpStream (and &File, etc.) is a hack that only helps in certain scenarios and causes trouble in others.