Boxed trait object doesn't implement trait

Hello, I'm attempting to write a library that accepts arbitrary connection types, each of which implements AsyncRead and AsyncWrite. Here is my code:


pub trait SyncConnection: AsyncRead + AsyncWrite + Latency {}
type Connection = Box<dyn SyncConnection>;


struct ReadConnection {
    key: Peer,
    conn: ReadHalf<Box<dyn SyncConnection>>,
    buff: Vec<u8>,
    device: String,
}

async fn read_one_record<S: AsyncRead + Latency>(
    mut conn: ReadConnection,
) -> (ReadConnection, Result<usize>) {
let res = conn.conn.read_exact(&mut header).await;
...
}

I would expect that wrapping these traits inside my SyncConnection trait would allow read_exact to be called on the ReadHalf type, which implemenents AsyncRead when its inner value implements AsyncRead, but the compiler give this objection:

error[E0599]: the method read_exact exists for struct futures::io::ReadHalf<Box<(dyn S yncConnection + 'static)>>, but its trait bounds were not satisfied
--> bramble-sync/src/sync.rs:514:33
|
514 | let res = conn.conn.read_exact(&mut conn.buff[4..]).await;
| ^^^^^^^^^^ method cannot be called on futures::io: :ReadHalf<Box<(dyn SyncConnection + 'static)>> due to unsatisfied trait bounds
|
::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.25/src
/io/split.rs:11:1
|
11 | pub struct ReadHalf {
| ----------------------
| |
| doesn't satisfy _: AsyncRead
| doesn't satisfy _: futures::AsyncReadExt
|
= note: the following trait bounds were not satisfied:
futures::io::ReadHalf<Box<(dyn SyncConnection + 'static)>>: AsyncRead
which is required by futures::io::ReadHalf<Box<(dyn SyncConnection + 'static )>>: futures::AsyncReadExt

So even though SyncConnection is defined as implmenting AsyncRead, the compiler doesn't think that the type Box<(dyn SyncConnection + 'static)> implements it. Any idea how I can make this work? Any help is appreciated.

Just implement the trait for Box<dyn SyncConnection>. (Arbitrary wrappers for dyn Trait don't – can't – implement the trait automatically. We know that Box is just an owning pointer, but many other T<dyn Trait> parametric types can be arbitrarily complex, so the trait can't just be implemented automatically in general.)

2 Likes

More generally, with such a trait alias; you are missing the blanket impl part; which, assuming the aliasees (such as Latency), are Box-transitive[1], would give you your missing Box-transitivity[1:1] for SyncConnection:

pub
trait SyncConnection
where
    Self : AsyncRead + AsyncWrite + Latency,
{}
impl<T : ?Sized> SyncConnection for T
where
    Self : AsyncRead + AsyncWrite + Latency,
{}

  1. impl<T : ?Sized + Trait> Trait for Box<T> { ... } ↩ī¸Ž ↩ī¸Ž

2 Likes

Thank you, these replies were very helpful!

@Yandros It hadn't occurred to me use a generic implementation such as this, which I see allows the library's users to avoid having to implement SyncConnection explicitly.

I'm attempting to implement this and am running into difficulty in a test, in which the test's concrete type which implements SyncConnection is not accepted by a function which requires dyn SyncConnection. The compiler message is:

error[E0308]: mismatched types
    --> bramble-sync/src/sync.rs:2818:69
     |
2818 |         let mut controller = SimpleController::new(name, file, key, conns);
     |                              ---------------------                  ^^^^^ expected
 trait object `dyn SyncConnection`, found struct `Duplex`
     |                              |
     |                              arguments to this function are incorrect
     |
     = note: expected struct `Vec<(bramble_crypto::PublicKey, Box<(dyn SyncConnection + '
static)>, Vec<_>)>`
                found struct `Vec<(bramble_crypto::PublicKey, Box<Duplex>, Vec<_>)>`
note: associated function defined here
    --> bramble-sync/src/simple_controller.rs:31:12
     |
31   |     pub fn new(
     |            ^^^
...
35   |         conns: Vec<(PublicKey, Connection, Vec<ID>)>,
     |         --------------------------------------------


I've confirmed that Duplex implements SyncConnection; attempting to implement it manually results in a conflicting implementation error. So as far as I understand, Duplex should meet the requirements to be interpreted as dyn SyncConnection when provided as an argument to SimpleController::new()

Box<Duplex> is not the same type as Box<dyn SyncConnection>. It's as simple as that. dyn Trait is not subtyping. Duplex is neither the same as nor the subtype of dyn SyncConnection. You'll have to perform the unsizing coercion explicitly, by collecting into a new Vec of 3-tuples (PublicKey, Box<dyn SyncConnection>, Vec<_>).

(Also, just think about it: trait object coercion can't possibly be transitive, "shining through" collections like that. dyn Trait is by definition dynamically-sized. You can create a Vec<Duplex> but there's no chance you could create a Vec<dyn SyncConnection>, since dyn SyncConnection can come from values of any size, provided they implement the trait.)

1 Like

Thank you, it turns out this was an easy fix. The vector was being constructed as so:

let conns = vec![(p, dup_a, ids)];

Which just needed to be rewritten to specify that the vector held trait objects, not any concrete type.

let conns: Vec<(PublicKey, Box<dyn SyncConnection>, Vec<ID>)> = vec![(p, dup_a, ids)];

I'm going a little off-topic here, but in my situation where the implementation of SyncConnection on Box needs to delegate the calls to the trait functions poll_read and poll_write to the contained trait object, it appears to be difficult to gain access to the trait object once it is boxed, since the first argument to poll_read will actually be Pin<&mut Box>. My first attempt at implementing AsyncRead for Box looked like this:

pub type Connection = Box<dyn SyncConnection>;
impl AsyncRead for Connection {
    fn poll_read(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &mut [u8],
    ) -> std::task::Poll<std::io::Result<usize>> {
        Pin::new(&mut self).poll_read(cx, buf)
    }
}

It's now obvious to me that this is actually a recursive function, and quite useless, although it is interestingly a kind of recursive function that the compiler is not able to recognize as not having a base case. My next attempt used this line:

        Pin::new(&mut self).as_deref_mut().poll_read(cx, buf);

The documentation for Pin seemed like this should have worked, since it should convert Pin<&mut Pin<Box>> to Pin<&mut SyncConnection>, which is exactly the type poll_read needs, but this line was also recursive.

My question is, is there a way to call the poll_read function of the trait object from the poll_read of the Boxed trait object?

I was able to solve this problem using a wrapper struct and unsafe code:

struct Connection {
    inner: Box<dyn SyncConnection>,
}

impl Connection {
    fn new(conn: Box<dyn SyncConnection>) -> Self {
        Self { inner: conn }
    }
}

impl AsyncRead for Connection {
    fn poll_read(
        mut self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
        buf: &mut [u8],
    ) -> std::task::Poll<std::io::Result<usize>> {
        (unsafe { self.map_unchecked_mut(|it| &mut *it.inner) }.as_mut()).poll_read(cx, buf)
    }
}

The unsafe function Pin::map_unchecked_mut() has the following warning: "This function is unsafe. You must guarantee that the data you return will not move so long as the argument value does not move (for example, because it is one of the fields of that value), and also that you do not move out of the argument you receive to the interior function."

I believe that I am not violating this guarantee, because that closure |it| &mut *it.inner won't be moving the Box<dyn SyncConnector>, rather it replaces Box<dyn SyncConnector> with &mut dyn SyncConnector, which I think is safe.

For that to be sound, you'd have to add a PhantomPinned marker to Connection. Otherwise, the caller could unpin the Connection and invalidate the backing memory of the dyn SyncConnection.

Alternatively, have you tried your original code, just with ReadHalf<Box<dyn SyncConnection>> replaced with ReadHalf<Pin<Box<dyn SyncConnection>>>? Some testing suggests that that should work.

1 Like

Yes, using ReadHalf<Pin<Box<dyn SyncConnection>>> does work; it also simplifies my code and avoids unsafe. Thanks for your help.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.