Is there a way to get the bounds of a RangeFull?

In std::ops, we have the RangeBounds trait, which provides the methods that return the start and end bounds of all built-in range types.

let range = 10..20;
let bound = range.start_bound();
println!("{:?}", bound); // Included(10)

let range = ..20;
let bound = range.start_bound();
println!("{:?}", bound); // Unbounded

But RangeFull, despite also implementing RangeBounds, yields an error.

let range = ..;
let bound = range.start_bound(); // cannot infer type for type parameter `T`
println!("{:?}", bound);

It seems that the compiler needs to know the explicit type of T for the Bound<&T> that start_bound returns.
However, RangeFull has no associated type parameter, and its implementation of RangeBounds returns Bound::Unbounded for both methods.

Is there a way to get the start_bound or end_bound for a RangeFull instance?

For context, I encountered this issue when writing a generic function that would print the bounds for any range instance, using RangeBounds as the generic type bound.

Sure, use a syntax like this: RangeBounds::<T>::start_bound(&(..))

1 Like

This still requires T to be an explicit type, that the compiler is unable to infer.

Using _ will result in the following:

let bound = RangeBounds::<_>::start_bound(&(..));
// consider giving `bound` the explicit type `Bound<&T>`, with the type parameters specified
println!("{:?}", bound);

And using it in a generic function will result on the same error as before:

use std::ops::RangeBounds;
use std::fmt::Debug;

fn print_bound<T: Debug, U: RangeBounds<T>>(range: &U) {
    let bound = RangeBounds::<T>::start_bound(range);
    println!("{:?}", bound);

fn main() {
    let range = 10..20;
    print_bound(&range); // Included(10)
    let range = ..;
    print_bound(&range); // cannot infer type for type parameter `T`

That's because there's nothing that lets the compiler infer the type of that range in the main function

1 Like

The compiler can infer the type just fine as long as you give it some information to work with, like in the first part of main, where it sees integer literals and can infer the type i32 (which is the default for integer literals if no other type is inferred). But how could you expect it to infer a specific type in the second half? If you have a specific type Foo in mind, you can fix it as follows:

    let range: RangeFull<Foo> = ..;

(Edit: my bad, RangeFull doesn't take a type parameter so you can't do it this way. But the following strategy still works.)

Or accomplish the same thing in one line with a turbofish:

    print_bound::<Foo, _>(&(..));

Further edit: yeah, this still prints Unbounded, sorry. There's not a trait in std for types that have a maximum/minimum value, but all the primitive integer types have associated constants T::MAX_VALUE and T::MIN_VALUE, which can be used for this purpose if you don't need to be generic. If you do need to be generic then you can use the Bounded trait from the num crate.

Oh, I see it. The reply above also makes more sense now.

let range = ..;
let bound = RangeBounds::<i32>::start_bound(&range);
println!("{:?}", bound); // Unbounded

I cannot specify a type for RangeFull, but I can specify one for the RangeBounds trait.

Well, I still think that the compiler should not need the range type, since RangeFull can only return Unbounded, but I understand where the problem comes from.

Thanks to all for your replies.

1 Like

You're welcome -- see my edited post if you want to get something other than Unbounded (sorry for not reading your post closely before writing!).

There are many "valid" programs that a type system can't prove to be correct. Rust is no exception – even though the implementation of RangeFull::start_bound() always dynamically returns Unbounded, the compiler can't possibly know that based on its static interface. When you are using a generic function, it is not checked what it exactly does – otherwise, all generic functions would be a leaky abstraction. It is only checked whether the types are unambiguous and whether they line up.

Yes, I later realized that the compiler absolutely needs to know the range type for RangeFull, despite it only returning Unbounded.

Even if Bound<T>::Unbounded carries no data, the size in memory of Bound<T> depends on the size of T.