Implicit ?Sized in tuples?

Consider this code

trait Aaa {
    fn bbb(arg:      (Self,)) ->      (Self,); // OK
    fn ccc(arg: Tuple<Self> ) -> Tuple<Self> ; // Error
}

struct Tuple<T>(T);

playground

The error can be overcome by either of

  • Adding a bound of T: ?Sized to Tuple
  • Adding a bound of Self: Sized to ccc

So it looks like there is an implicit ?Sized bound on tuple items.

What is going on here?

There are a few other places where similar requirements were being overlooked before but will now be enforced. For example, a number of traits like the following were found in the wild:

trait Foo {
    // currently accepted, but should require that Self: Sized
    // Should update: well, this doesn't compile now :)
    fn method(&self, value: Option<Self>);
}

To be well-formed, an Option<T> type requires that T: Sized. In this case, though T=Self, and Self is not Sized by default. Therefore, this trait should be declared trait Foo: Sized to be legal. The compiler is currently attempting to enforce these rules, but many cases were overlooked in practice.

src: 1214-projections-lifetimes-and-wf - The Rust RFC Book

Note RFC 1214 carries too many no longer compiled examples... But the basic idea is worth reading.

And for Sized in std::marker - Rust

  • All type parameters have an implicit bound of Sized .
  • The one exception is the implicit Self type of a trait. A trait does not have an implicit Sized bound ...

So the wf rule enforces here.

1 Like

The error message isn't great here, but the semantics are:

trait Aaa {
    // `Self` is `?Sized`
    fn bbb(arg:      (Self,)) ->      (Self,); // OK
    fn ccc(arg: Tuple<Self> ) -> Tuple<Self> ; // Error
}

struct Tuple<T>(T); // `T` is `Sized` and thus so is `Tuple<T>`

And the error is because you can't "use" Self: Sized without declaring it, similar to how you can't clone here without declaring U: Clone:

fn foo<U>(u: U) {
    let _ = u.clone();
}

It's a problem with the possible implementors of the trait (as per the trait header) not being restricted enough for the function signature to work, because an unsized implementor can't meet the (implicit) bounds on your Tuple<Self>. (You could also add where Self: Sized to the function.)


That leaves the question, why doesn't bbb complain? The compiler intentionally doesen't prevent declarations that take or return unsized types, for example this also compiles. The reason is that they hope to some day allow passing unsized types or having unsized locals to some extent. If you try to actually implement the trait, trying to define the function will result in a compilation error.

That's also why adding T: ?Sized to Tuple fixes things.


The last field of a tuple is allowed to be unsized, which makes the tuple type unsized, if that's what you mean.

2 Likes

And, to amend @quinedot's answer, an example of constructing an unsized tuple:

fn test(parameter: &(u32, [u8])) {
    dbg!(parameter);
}

fn demo() {
    let x: (u32, [u8; 3]) = (42, [1, 2, 3]);
    test(&x);
}

oh, wait, it doesn’t actually work o.O

error[E0658]: unsized tuple coercion is not stable enough for use and is subject to change
 --> src/lib.rs:7:10
  |
7 |     test(&x);
  |          ^^
  |
  = note: see issue #42877 <https://github.com/rust-lang/rust/issues/42877> for more information

well, I never stop learning new things! What is “not stable enough” even supposed to mean? In my mind stable vs. unstable was a binary thing…

2 Likes

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.