Why ops::Index<Idx> allows unsized Idx?

#1

Today I’ve noticed that Index is defined as follows:

pub trait Index<Idx: ?Sized> {
    #[stable(feature = "rust1", since = "1.0.0")]
    type Output: ?Sized;

    #[stable(feature = "rust1", since = "1.0.0")]
    fn index(&self, index: Idx) -> &Self::Output;
}

why do we need Idx: ?Sized if we accept index by value anyway? Shouldn’t https://github.com/rust-lang/rust/pull/23601 removed this anti-bound as well?

0 Likes

#2

One can use unsized_locals for this… Here’s just an example to make it compile:

#![feature(unsized_locals)]

use core::ops::Index;
use core::fmt::Debug;

type A<'a> = &'a dyn Index<dyn Debug, Output = u8>;

fn main() {
    let x: A<'_> = panic!();
    let y: dyn Debug = *(&0 as &dyn Debug);
    x.index(y);
}

If that is useful remains to be seen. However, it is a breaking change to enforce Idx: Sized.

0 Likes

#3

I wonder if there’s real code that breaks. rust-lang/rust test suite passes if I remove the bound.

EDIT: which probably means that we either need to remove the bound, or to add a test for it.

0 Likes

#4

Unlikely I think because of the lack of stable unsized_locals. However, I rather leave the un-bound in for future-compat purposes. Is there a specific reason you want to remove ?Sized?

I would happily r+ a PR that adds a test! Thank you for discovering the lack thereof.

1 Like

#5

Consistency: we generally don’t have ?Sized bounds unless there’s a strong reason to have one. For example, Add is defined as trait Add<RHS=Self>.

I definitely think that we should not blindly add a test for this. I guess I’ll send a PR for the libs team to review. It may be the case that we want this to be ?Sized, but than we need to figure out if we can change over operators to follow the suite.

EDIT: PR is up https://github.com/rust-lang/rust/pull/59527

0 Likes

#6

Hmm… I mostly was looking for a use-case. :wink: Consistency is valuable, but I wouldn’t want to reduce future potential expressiveness just for that.

The test can easily be removed whenever, there’s no problem in adding it now.

1 Like

#7

That’s an extremely personal opinion, but I prefer to reduce expressiveness of things unless there’s a strong reason that expressiveness is required :slight_smile: Expressiveness and “easy to understand” tend to oppose each other (https://www.tedinski.com/2018/01/30/the-one-ring-problem-abstraction-and-power.html is a good blog post on this topic).

In this particular cases, I imagine that, with by-value DSTs implemented, someone might accidentally write Index<[usize]> instead of Index<&[usize]> for multidimensional array indexes, and get an excessive stack usage.

1 Like

#8

I think this is a case of “I cannot tell the future”. :stuck_out_tongue:

Seems lintable? We can test this out and see if it actually becomes a problem.

0 Likes

#9

I’d also make Add a subtrait of Sized then. Self and RHS are almost symmetric but only RHS has the Sized bound.

1 Like

#10

DSTs are always passed by pointer, I don’t see how fn index(&self, a: [usize]) will cause excessive stack usage.

0 Likes

#11

The current implementation of unsized_locals is naïve and may allocate unnecessary variable-length temporaries to move the slice into.

0 Likes