Can you drive type inference through generic type default of type alias?

In my actual code I have some type Complex<A, B, C> and I want to set a default C = usize since C is an index-like type. I tried to achieve this through a type alias but realized that the default from the type alias seems to be ignored/not take priority. Here is a simplified example.

struct Wrap<T>(T);

impl<T> Wrap<T> {
    pub fn new(t: T) -> Self{
        Self(t)
    }
}

impl<T> Wrap<T> where T: Copy + Into<usize> {
    fn to_usize(&self) -> usize {
        self.0.into()
    }
}

type WrapUsize<T = usize> = Wrap<T>;

type WrapUsize2 = Wrap<usize>;

let out = Wrap::<usize>::new(1).to_usize(); // Compiles.
let out = WrapUsize::new(1).to_usize(); // Does not compile.
let out = WrapUsize2::new(1).to_usize(); // Compiles, but now I can not override the default through `WrapUsize2`.

In the line:

let out = WrapUsize::new(1).to_usize(); // Does not compile.

the type of out resolves to Wrap<i32>, with i32 being the default type for integer literals. Compilation fails because Wrap<T>::to_usize requires T: Copy + Into<usize> which i32 does not implement.

However, I expect the type of out to resolve to Wrap<usize>, because I expect the type alias to force Wrap::new to resolve to Wrap::<usize>::new. What is going on?

1 Like

This works:

let _out = <WrapUsize>::new(1).to_usize();

One of the more baffling things I've seen in Rust. I guess defaults only apply in type context.

1 Like

It's not a type alias issue; you'll get the same behavior if you put the defaulted parameter on your nominal type directly.

Inference doesn't fall back to defaults, but there are some workarounds. For your sample code, that would be:

    let out = <WrapUsize>::new(1).to_usize(); // Compiles now

With multiple type parameters, it can look like so.

    let out = Wrap::<_, _, usize>::new((), (), 1).to_usize();
    let out = WrapUsize::<_, _>::new((), (), 1).to_usize();
    let out = <WrapUsize<_, _>>::new((), (), 1).to_usize();

In short you need the qualified type expression (starts with <...>:: like <WrapUsize<_, _>>::) or a non-empty turbofish (WrapUsize::<_, _>) for the defaults to take effect. More details are in this section.

3 Likes

Thank you for the additional information.

I was hoping to avoid having the library consumer write <_, _, ...>, but it seems like there is currently still no way around it.

If I was a consumer of the examples, I'd probably write

let out = WrapUsize::new((), (), 1_usize).to_usize();

Yes, that is a reasonable way of writing it. Here is how that would look in the actual code:

let data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let view = Array::new(data, SquareSymmetric(Static::<4>));
assert_eq!(view[(0usize, 0usize)], 0); // First access
assert_eq!(view[(1, 0)], 1); // Further accesses

The multi-dimensional array index is a tuple rather than an array so that the index type can be different for each dimension.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.