Method not found when passing generic struct

⚓ rust    📅 2025-05-17    👤 surdeus    👁️ 4      

surdeus

Warning

This post was published 43 days ago. The information described in this article may have changed.

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.

2 posts - 2 participants

Read full topic

🏷️ rust_feed