Strange type inference around FnOnce & Fn

⚓ Rust    📅 2025-10-02    👤 surdeus    👁️ 7      

surdeus

The following is a reduced reproducer: Rust Playground

use std::io::Read;

pub trait HttpClient {
    fn get(&self, url: &str) -> Result<Box<dyn Read>, std::io::Error>;
}

struct DummyHttpClient<T> {
    responder: T,
}

impl<T> HttpClient for DummyHttpClient<T>
where
    T: Fn(&str) -> Result<Box<dyn Read>, std::io::Error>,
{
    fn get(&self, url: &str) -> Result<Box<dyn Read>, std::io::Error> {
        (self.responder)(url)
    }
}

struct UserBuilder {
    client: Option<Box<dyn HttpClient>>,
}

impl UserBuilder {
    pub fn new() -> Self {
        Self { client: None }
    }

    /// Set the HTTP client to use for requests.
    pub fn http_client(mut self, client: Box<dyn HttpClient>) -> Self {
        self.client = Some(client);
        self
    }
}

fn test_custom_http_client() {
    let http_client = Box::new(DummyHttpClient {
        responder: |url| {
            Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("DummyHttpClient cannot fetch {url}"),
            ))
        },
    });
    let client = UserBuilder::new().http_client(http_client);
}

There are build errors that FnOnce and Fn are not general enough:

error: implementation of `FnOnce` is not general enough
  --> src/lib.rs:45:49
   |
45 |     let client = UserBuilder::new().http_client(http_client);
   |                                                 ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 str) -> Result<Box<dyn std::io::Read>, std::io::Error>` must implement `FnOnce<(&'1 str,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 str,)>`, for some specific lifetime `'2`

error: implementation of `Fn` is not general enough
  --> src/lib.rs:45:49
   |
45 |     let client = UserBuilder::new().http_client(http_client);
   |                                                 ^^^^^^^^^^^ implementation of `Fn` is not general enough
   |
   = note: closure with signature `fn(&'2 str) -> Result<Box<dyn std::io::Read>, std::io::Error>` must implement `Fn<(&'1 str,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `Fn<(&'2 str,)>`, for some specific lifetime `'2`

Adding

  where
    T: Fn(&str) -> Result<Box<dyn Read>, std::io::Error>,

to the struct makes the issue go away: Rust Playground

Why is this? As I understand it, best practise is to only add bounds to impls that need it in rust. And here only the impl of HttpClient should need the bound. Yet that is not the case here?

3 posts - 2 participants

Read full topic

🏷️ Rust_feed