Summary
Extend higher-ranked trait bounds to types.
Motivation
When working with closures, it would be useful if anonymous functions could have generic bodies that were reusable across multiple different parameter types. This would broadly speed up development and reduce human error by getting rid of redundancy, and may have other use cases that I haven't considered.
For example, say you have a Filter
trait and you want to reuse the same function body for a few different Filter
types:
struct Counter<I> {
a: fn(I, FilterAll) -> usize,
b: fn(I, FilterTrue) -> usize,
}
fn counter<I, F>(f: fn(I, F) -> usize) -> Counter<I>
where
I: Iterator<Item=bool>,
F: Filter,
{
let a: fn(I, FilterAll) -> usize = f;
let b: fn(I, FilterTrue) -> usize = f;
Counter { a, b }
}
let c = counter(|iter, filter| {
iter.filter(|x| filter.has(x))
.count()
});
As I see it, this would be a natural extension to higher-ranked trait bounds in which the parameters are part of the type itself:
let f: for<'a> fn(&'a i32, &'a i32) -> &'a i32 = |a, b| {
if a > b { a } else { b }
};
Specifics
Types can be referenced with unspecified generic parameters. For instance, for<T> Option<T>
would count as a type in the same way that for<'a> fn(&'a i32)
does.
fn assert_is_some(option: for<T> Option<T>) {
assert!(option.is_some());
}
In this case, for<T> Option<T>
is known to be covariant over T
and therefore can't be downcast into a more specific Option<_>
. However, any subtype such as Option<i32>
can be implicitly upcast into for<T> Option<T>
, in much the same way that &'static i32
can be upcast into &'a i32
:
assert_is_some(Some(123));
These higher-ranked parameters can also be bounded by traits or lifetimes to refine their possible subtypes.
Referencing a generic function without specifying its generic parameters would produce the type for<..> fn(..)
, similar to how it does for lifetimes. Function types are contravariant over their parameters, and therefore can be downcast into a function type with more specific parameters (but not the other way around).
fn sum<T>(a: T, b: T) -> T
where
T: std::ops::Add<Output = T>
{
a + b
}
let f: for<T: std::ops::Add<Output=T>> fn(T, T) -> T
= sum;
assert_eq!(f(1_i32, 2), 3);
assert_eq!(f(1_u64, 2), 3);
It's also possible that type identifiers like Option
by itself could be sugar for for<T> Option<T>
, in the same way that Ref
is sugar for for<'a> Ref<'a>
. However, this might make code harder to follow and could lead to confusion by new users, so it's tentative.
Alternatives
In the original example, one could instead add a separate argument for each case (fn(I, FilterAll) -> bool
and fn(I, FilterTrue) -> usize
), and simply force users to either copy-paste their closure or reuse it with a function. However, this means that adding more cases to Counter
would require a new constructor and possibly a breaking change.
Another option is to use an enum instead of a generic type, but this comes at a runtime cost in terms of performance and variant enforcement.