Default types for generics to improve type inference?


#1

Consider a function with a type signature like this:

fn func<P: AsRef<Path>>(path: Option<P>)

Such a function might call an underlying FFI function that accepts a const char *, mapping None to NULL.

Calling that function with a None parameter doesn’t provide enough information for type inference; the compiler doesn’t know the type of P inside the Option. This typically requires an explicit None as Option<SomeType>, a type specified for func, or in the future a type ascription; all of those seem far less convenient than just saying None.

However, the function itself handles None identically regardless of the type P.

Given that, I wonder if Rust could provide some way to simplify this, and allow func(None) to work without further type information. As one possibility, what if there were a way to say “if you don’t know what type to use, and any type will do, use this one”. For instance, in the call above, the default could be Option<&Path>.

Does that seem plausible? Or, does anyone see another plausible route to support func(None) without further type information?


#2

There is an unstable feature that enables this, feature(default_type_parameter_fallback). Its future is uncertain due to unresolved issues about how to handle multiple conflicting fallbacks for the same type inference variable.


#3

Also, you can usually use None::<i32> or whatever, which is a bit shorter than None as Option<i32>.


#4

There is an unstable feature that enables this, feature(default_type_parameter_fallback). Its future is uncertain due to unresolved issues about how to handle multiple conflicting fallbacks for the same type inference variable.

That looks like exactly what I’d hoped for, thanks!

Is there some way to enable this feature and set a default iff in an unstable compiler, without duplicating the entire declaration or function body?

  • Josh Triplett

#5

That is not possible unfortunately. Well, technically it is, because the type parameter defaults are only warnings now. But they say they will be errors soon. The reason is that type parameter defaults were allowed even when they had no effect (they have no effect on functions for example without the unstable feature enabled).


#6

That is not possible unfortunately. Well, technically it is, because the type parameter defaults are only warnings now. But they say they will be errors soon. The reason is that type parameter defaults were allowed even when they had no effect (they have no effect on functions for example without the unstable feature enabled).

OK. In that case, I think my best shot may be defining the underlying function with a different name, and then either exporting it with the correct name on stable compilers or creating a wrapper with a type default on unstable compilers.


#7

What about something akin to Scala’s Nothing? It would be a compiler-generated enum with no variants that implements every* trait “automagically”. When the type inference algorithm fails to infer a type parameter it would choose the Nothing type.

There are a quite few problems with this approach though. Automatically implementing trait methods is not really a problem, since the Nothing type cannot be instantiated, at least when not explicitly transmuting into it. The implementations can simply be placeholders that cause a runtime panic in case the user did somehow manage to get an instance of the type. Problems arise with associated functions, associated types and associated constants. Limiting Nothing to only implement traits that do not contain any of these associated items similarly to how trait objects only work with object-safe traits might be an option.


Alternatively, instead of having a single Nothing type, it could be an anonymous type that is automatically derived and constructed from the bounds of the unresolved type parameter if possible.

For example, given the function in the original post:

fn func<P: AsRef<Path>>(path: Option<P>);

A call like this:

func(None);

Because the type parameter could not be inferred, would translate to this:

func::<$Anon1>(None::<$Anon1>);



enum $Anon1 {}

// AsRef has no associated items and can be safely derived (?).
impl AsRef<Path> for $Anon1 {
    fn as_ref(&self) -> &Path {
        // This should never occur
        unreachable!()
    }
}

There are probably many more problems with both approaches that I haven’t thought of.


#8

Static trait methods can still be invoked on uninhabited types.


#9

Static (trait) methods and associated functions are the same thing, unless I misunderstand.


#10

Ah, sorry, missed that part.