Method not found when passing generic struct

Hi!

I have the following somewhat convoluted code below. I tried doing a more minimal reproducible example for the playground but I can't hit it, unfortunately (attempt here). Apologies for that, truly.

I guess it would be easier to describe - I have a builder with two fields, both of which depend on a generic parameter and can be optional. I have generic methods in the builder that consume the builder and return a new builder from a different type, replacing one of the field types with another and keeping the other field. (Ignore simplification opportunities, but feedback welcome if you have it).

pub struct Builder<TCPFilter, TCPRouter, UDPHandler>
where
    TCPFilter: crate::TCPFilter + Send + 'static,
    TCPRouter: crate::TCPRouter + Send + 'static,
    UDPHandler: crate::UDPHandler + Send + 'static,
{
    tcp_stack: Option<TCPStack<TCPFilter, TCPRouter>>,
    udp_stack: Option<UDPStack<UDPHandler>>,
}

impl<TCPFilter, TCPRouter, UDPHandler> Builder<TCPFilter, TCPRouter, UDPHandler>
where
    TCPFilter: crate::TCPFilter + Send + 'static,
    TCPRouter: crate::TCPRouter + Send + 'static,
    UDPHandler: crate::UDPHandler + Send + 'static,
{
    pub fn new() -> Self {
        Self {
            tcp_stack: None,
            udp_stack: None,
        }
    }

    // Keep the UDPHandler type, but replace the others
    pub fn with_tcp_stack<H, R>(self, tcp_handler: H, tcp_router: R) -> Builder<H, R, UDPHandler>
    where
        H: crate::TCPFilter + Send + 'static,
        R: crate::TCPRouter + Send + 'static,
    {
        return Builder {
            tcp_stack: Some(TCPStack::new(tcp_handler, tcp_router)),
            udp_stack: self.udp_stack,
        };
    }

    // Keep the TCPFilter and Router types, but replace the UDPHandler
    pub fn with_udp_stack<Handler>(
        self,
        udp_stack: Handler,
    ) -> Builder<TCPFilter, TCPRouter, Handler>
    where
        Handler: crate::UDPHandler + Send + 'static,
    {
        return Builder {
            tcp_stack: self.tcp_stack,
            udp_stack: Some(UDPStack::new(udp_stack)),
        };
    }
}

Using the builder works like this:

pub struct PassthroughUDP {}

impl UDPHandler for PassthroughUDP {...}

pub struct PassthroughTCP {}

impl TCPFilter for PassthroughTCP {...}

// Skipping TCPRouter to shorten

fn main() {
    builder() // Creates a Builder with some "default" types 
         // Change the TCP-related types
         .with_tcp_stack(
             PassthroughTCP::new(),
             MyTCPRouter::new()
         )
         // Change the UDP-related types
         .with_udp_stack(PassthroughUDP::new())
         .build();
}

This all compiles fine with the example Handler/Filter implementations above, which are different than the "default" types of the builder we get through builder().

The problem comes when I try to pass an instantiation of the following generic struct:

pub struct InspectTCP<S, D>
where
    S: for<'a> FnMut(&'a [u8]) + Send + Clone + 'static,
    D: for<'a> FnMut(&'a [u8]) + Send + Clone + 'static,
{
    inspect_source: S,
    inspect_destination: D,
}

impl<S, D> InspectTCP<S, D>
where
    S: for<'a> FnMut(&'a [u8]) + Send + Clone + 'static,
    D: for<'a> FnMut(&'a [u8]) + Send + Clone + 'static,
{
    pub fn new(inspect_source: S, inspect_destination: D) -> Self {
        Self {
            inspect_source,
            inspect_destination,
        }
    }
}

impl<S, D> TCPFilter for InspectTCP<S, D>
where
    S: for<'a> FnMut(&'a [u8]) + Send + Clone + 'static,
    D: for<'a> FnMut(&'a [u8]) + Send + Clone + 'static,
{ ... }

// Use

fn main() {
    let source_reader = |bytes_read| {
        // whatever
    };

    let destination_reader = move |bytes_read| {
        //whatever
    };

    builder()
        .with_tcp_stack(
            InspectTCP::new(source_reader, destination_reader),
            MyTCPRouter::new()
        )
        .with_udp_stack(PassthroughUDP::new())
        .build();
}

Error:

Error[E0599]: no method named `with_udp_stack` found for struct `Builder<InspectTCP<{closure@main.rs:205:25}, {closure@...}>, ..., ...>` in the current scope
   --> src/main.rs:230:10
    |
216 |       builder()
    |  _______________-
217 | |         ...
...   |
230 | |         .with_udp_stack(PassthroughUDP::new())
    | |         -^^^^^^^^^^^^^^ method not found in `Builder<InspectTCP<{closure@main.rs:205:25}, {closure@...}>, ..., ...>`
    | |_________|
    |
    |
    = note: the method was found for
            - `Builder<TCPFilter, TCPRouter, UDPHandler>`

Based on what I gathered as research so far, I might be messing up the lifetime params of the reference inside the FnMut. But then again, why doesn't the compiler report type mismatch? I tried removing the for<'a> bounds as well but with no luck. What am I missing? Could this be a compiler bug? I tested on both 1.86 and 1.87.

EDIT: If I change from FnMut(&[u8]) to FnMut(Vec<u8>) (and the corresponding implementation to make that work), everything compiles fine. More evidence the ref lifetime is probably causing trouble here.

The compiler is bad at figuring out when arguments to a closure should take any lifetime (and really bad at figuring out what to do when you try to return such an argument). So bad that there is special inference for when you pass a closure directly to a function with a Fn trait bound on the argument.

So I suspect either of these will work:

-    let source_reader = |bytes_read| {
+    let source_reader = |bytes_read: &_| {
         // whatever
     };

-    let destination_reader = move |bytes_read| {
+    let destination_reader = move |bytes_read: &_| {
         //whatever
     };
-    let source_reader = |bytes_read| {
-        // whatever
-    };

-    let destination_reader = move |bytes_read| {
-        //whatever
-    };

     builder()
         .with_tcp_stack(
-            InspectTCP::new(source_reader, destination_reader),
+            InspectTcp::new(
+                |bytes_read| {
+                    // whatever
+                },
+                |bytes_read| {
+                    // whatever
+                },
+            ),
             MyTCPRouter::new()
         )
         .with_udp_stack(PassthroughUDP::new())
         .build();

(Your playground also exhibits the error when you move the closure out of the invocation site.)

You are spot on, thank you very much. Do you think it is worth opening an issue on the compiler team for this?

They're aware of this problem. Here's an existing issue though. There are probably others.

1 Like