Why None still require inference on the type of Option<T>

Take the following code

let repositories: InstallationRepositories = auth_octocrab
            .get("/installation/repositories", None)
            .await?;

This code errors

88   |             .get("/installation/repositories", None)
     |              ---                               ^^^^ cannot infer type of the type parameter `T` declared on the enum `Option`
     |              |
     |              required by a bound introduced by this call
     |
     = note: cannot satisfy `_: authentication::_::_serde::Serialize`
     =

Because of the definition of get

pub async fn get<R, A, P>(&self, route: A, parameters: Option<&P>) -> Result<R>
    where
        A: AsRef<str>,
        P: Serialize + ?Sized,
        R: FromResponse,
    {
        self.get_with_headers(route, parameters, None).await
    }

The compiler is asking for the type of P. But this makes no sense (in my mind) because I am passing None and thus P has no purpose in here.

Option is an enum made of Some(T) and None. Only the Some(T) should require disclosure on the type? The other (maybe more logical) explanation is that this bound is required by the function definition and not by Option. In this case, is it possible to refactor the function in such a way that you are no longer required to pass None::<&()> where () is just a fictitious type to satisfy the compiler.

1 Like

The issue is that the compiler cannot know from the code snippet which type parameters will have. It sure is an Option<&P> but there is no way to know for the compiler what P will be, since None is a variant of any possible Option<T> (and hence Option<&P>).

Rust is a statically typed language, so the types of each variable and parameter must be known at compile time.

3 Likes

None is syntax sugar for Option<T>::None. In other words, None is a variant for an enum with a generic parameter, therefore the compiler requires such generic parameter because it can't infer it in this situation.

2 Likes

In general the function could still depend on P in surprising ways, e.g. getting its TypeId, its size/alignment, and if it has certain trait bounds (e.g. Default or Deserialize) even call functions depending on it.

10 Likes

Does the method need to make a distinction between an empty parameter list and a missing one? If not, you may be able to change the method definition to:

pub async fn get<R, A, P>(&self, route: A, parameters: P) -> Result<R>
    where
        A: AsRef<str>,
        P: Serialize,
        R: FromResponse,
    {
        self.get_with_headers(route, parameters, None).await
    }

Serde provides implementations of Serialize for both () and &T, so callers that want to pass a reference can still do so and an empty parameter list can be provided like this:

let repositories: InstallationRepositories = auth_octocrab
            .get("/installation/repositories", ())
            .await?;
2 Likes

I understand that. But I think the dependency is coming from the Option<T> and not the function itself? That is, if Option<T> definition was Option only, then None would be accepted?

One would need an entirely different language for that to work. It's like saying “if Rust was a Python, except for syntax then this would have worked”… well, probably, who knows?

Difference between Rust where Option<bool>::None is one bytes while Option<f64>::None is sixteen bytes and hypothetical Rust where Option<T> doesn't exist and Option is non-generic type is too vast to know how that beast would have behaved.

2 Likes