I have noticed that the implementation of the Implied Bounds RFC is... progressing slowly. Personally I am uneasy about the proposed design, because it can lead to some very confusing long-range bound inference. The usage and declaration of bounds could even be in entirely different crates, though I'm unsure whether that was in the end accepted in the RFC. So there are two alternatives which look more reasonable in my view.
Allow omitting bounds only if the function doesn't make any use of them.
Here is an example from the RFC:
fn panic_if_not_borrowed<'a, B>(cow: Cow<'a, B>) -> &'a B
// where B: ToOwned
{
match cow {
Cow::Borrowed(b) => b,
Cow::Owned(_) => panic!(),
}
}
// ^ the trait `std::borrow::ToOwned` is not implemented for `B`
In this example, the function doesn't actually use the B: ToOwned
bound in any way, so no confusion can stem from omitting it. There are many similar utility functions in practice, which could be entirely bound-agnostic. In a sense, it would work just as where-clauses on methods: if the clauses are satisfied for the given type, then the method is present, otherwise it is deleted. Functions with implied bounds would similarly be available whenever the types are well-formed, and unavailable otherwise.
Introduce constraint functions
I believe I saw similar suggestions in the past, but I couldn't find any open or closed RFC for "constraint". The idea is that we allow declaring parametric constraints, like (bikeshed warning)
constraint FilteredIterator<I, F, B> = {
I: Iterator,
F: FnMut(I::Item) -> Option<B>,
};
Then we could reference the constraint on types, impls and functions:
fn filter_show<I, F, B>(it: I, f: F)
where FilteredIterator<I, F, B>,
B: Display,
{
for (n, item) in it.filter_map(f).enumerate() {
println!("pos {n}: {item}");
}
}
The example is obviously artificial, but e.g. in generic numeric code it's quite common to have bounds like
where
T: Add<T, Output=T> + for<'a> Add<&'a T, Output = T> + Sized,
for<'b> &'b T: Add<T, Output=T> + for<'a> Add<&'a T, Output = T>,
The trait part can at least be extracted into a new type:
trait Addable: Sized + Add<Self, Output=Self> + for<'a> Add<&'a Self, Output=Self> {}
impl<T> Addable for T
where T: Sized + Add<Self, Output=Self> + for<'a> Add<&'a Self, Output=Self>
{}
The bounds on references and associated types, unfortunately, generally cannot be simplified in the same way.
The syntax overhead of declaring constraints on usages is relatively minimal, but at the same time it is explicit, easily searchable, and composable for arbitrary usages (unlike implied bounds, which are supposed to work only for bounds on types).
The common reasoning for implied bounds is "we don't require you to write 'b: 'a
for functions fn f<'a, 'b>(_: &'a Foo<'b>)
". However, in that function declaration both 'a
, 'b
and the relation between them is explicit. Everyone knows that the lifetime of the inner type must outlive the lifetime of the reference. It is much more confusing when all type constructors are custom, and the bounds on generic parameters are also whatever defined in the crate, rather than something well-known and intuitively obvious.
Thoughts?