Techniques for working around implied bounds?

I have the following code:

trait UdpSocket {
    fn receive_icmp_error(&mut self, err: IcmpError);
}

trait BufferUdpSocket<B: BufferMut>: UdpSocket {
    fn receive_packet(&mut self, body: B);
}

trait UdpSocketContext {
    type Socket: UdpSocket;
}

trait BufferUdpSocketContext<B: BufferMut>: UdpSocketContext
where
    <Self as UdpSocketContext>::Socket: BufferUdpSocket<B>
{
}

The where bound on BufferUdpSocketContext is causing problems. Thanks to the lack of implied bounds, I have to propagate the where bound to call callers/sub-traits. In my codebase, this is 90 different code locations.

My question is: Is there some way to hack around this that approximates the same behavior but without having the bound be "viral" in this way? I'm happy to restructure the traits if need be, but at a minimum, I need:

  • One trait which does not take a B: BufferMut parameter, and has an associated Socket type
  • One trait which does take a B: BufferMut parameter, and has an associated Socket type (maybe its own, or maybe <Self as UdpSocketContext>::Socket) with a BufferUdpSocket<B> bound
  • The two Socket types in the two contexts are the same (ie, I can use them interchangeably in code and rustc will know that that's OK)
1 Like

Does this work for you?

// New constraint-satisfying trait with blanket impl
trait BufferedUdpSocketContext<B>: UdpSocketContext {}
impl<T: UdpSocketContext, B> BufferedUdpSocketContext<B> for T
where
    B: BufferMut,
    T::Socket: BufferUdpSocket<B>
{}

// Super-trait bound instead of where clause
trait BufferUdpSocketContext<B: BufferMut>: BufferedUdpSocketContext<B>
{
}
1 Like

Unfortunately it looks like it doesn't work. Maybe I'm missing something, though, in how you were intending it to be used?

I’ve never tried the following approach before (just came up with the idea), but it might work somewhat? …

trait A {
    type Ty;
}

trait B: A<Ty = <Self as B>::Ty1> {
    type Ty1: Foo;
}

trait Foo {}

fn foo<T: B>(x: T::Ty) {
    bar(x); // <- works!
}
fn bar<T: Foo>(x: T) {}

Edit: In other words, something like

trait BufferUdpSocketContext<B: BufferMut>: UdpSocketContext<Socket = Self::TheSocketType> {
    type TheSocketType: BufferUdpSocket<B>;
}

Feel free to try that, I’d be curious how good or bad rustc handles this approach :wink:

In case your actual BufferUdpSocketContext trait doesn’t already have a generic impl like

impl<T: ?Sized, B> BufferUdpSocketContext<B> for T
where
    B: BufferMut,
    T: UdpSocketContext,
    T::Socket: BufferUdpSocket<B>,
{
    type TheSocketType = T::Socket;
}

(e.g. because there are extra methods), you can spare the boilerplate of defining this TheSocketType manually for every impl, with an additional generically implemented helper trait, and make that a supertrait of BufferUdpSocketContext.

It’s called TheSocketType with the intention of not being used (because it’s supposed to be the same as Socket) but not causing ambiguities. Maybe something like “_Socket” would be a better name.

2 Likes

I was the one missing something, sorry.

I looked again and ended up exactly where @steffahn suggested above, threading the associated type along ala Iterator/IntoIterator.

Ah that works, thanks!

Next challenge, though :slight_smile: I can't make it work when multiple buffer types are used.

fn receive_packet<
    A: BufferMut,
    B: BufferMut,
    C: BufferUdpSocketContext<A> + BufferUdpSocketContext<B>,
>(
    sock: &mut <C as UdpSocketContext>::Socket,
    body_a: A,
    body_b: B,
) {
    sock.receive_packet(body_a);
    sock.receive_packet(body_b);
}
error[E0284]: type annotations needed: cannot satisfy `<C as UdpSocketContext>::Socket == _`
  --> src/lib.rs:30:4
   |
30 | fn receive_packet<
   |    ^^^^^^^^^^^^^^ cannot satisfy `<C as UdpSocketContext>::Socket == _`

Right… rustc isn’t great with these - there’s no really good support for reasoning about type equalities, but it can be worked around:

fn receive_packet<
    A: BufferMut,
    B: BufferMut,
    C: BufferUdpSocketContext<A>
        + BufferUdpSocketContext<B, BufSocket = <C as BufferUdpSocketContext<A>>::BufSocket>,
>(
    sock: &mut <C as UdpSocketContext>::Socket,
    body_a: A,
    body_b: B,
) {
    sock.receive_packet(body_a);
    fn helper_function<B: BufferMut, C: BufferUdpSocketContext<B>>(
        sock: &mut C::Socket,
        body_b: B,
    ) {
        sock.receive_packet(body_b);
    }
    helper_function::<B, C>(sock, body_b);
}

Edit: More symmetric alternative

fn receive_packet<
    S,
    A: BufferMut,
    B: BufferMut,
    C: BufferUdpSocketContext<A, BufSocket = S>
        + BufferUdpSocketContext<B, BufSocket = S>,
>(
    sock: &mut <C as UdpSocketContext>::Socket,
    body_a: A,
    body_b: B,
) {
    fn receive_packet<B: BufferMut, C: BufferUdpSocketContext<B>>(
        sock: &mut C::Socket,
        body_b: B,
    ) {
        sock.receive_packet(body_b);
    }
    receive_packet::<_, C>(sock, body_a);
    receive_packet::<_, C>(sock, body_b);
}

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.