Type validation: `type X = T` vs `let x: T`

Just a tiny curious bit: how come

type Tp = (u8, [str]);

doesn't alarm the compiler, yet

let tp: &(u8, [str]);

fails immediately with

// the size for values of type `str` cannot be known at compilation time
// the trait `Sized` is not implemented for `str`
// slice and array elements must have `Sized` type

Are the type and/or use declarations auto-deferred?

In struct declarations this also errors. I don't think there is any usecase where [str] could be valid, so you could probably file this as a bug. although i don't know if it would be fixed, as this theoretically would break code.
When you do

impl Trait for Tp {}

this also fails again.

Interestingly using [str] in a associated type of a trait makes the compiler suggest adding a ?Sized bound, which doesn't help at all.

1 Like

(u8, [str]) is a valid type, it just doesn’t have any values (because [str] doesn’t) and thus a reference to it can’t have either. It’s said to be uninhabited, like the never type ! and empty enums such as std::convert::Infallible). Sorry, that was inaccurate.

If it is a valid type why does it error if i write:

struct Test2([str]);

I can write the above with the never type (if i enable the feature), so those definitely behave different.

I see this more like array having a Sized bound (which str doesn't satisfy), so this isn't a valid type, but in type aliases this doesn't get checked. So i believe it is a bug (or at least a bad diagnostic).

2 Likes

Ah, very good point, I stand corrected. I think it’s related to the old limitation (#21903) that generic type aliases don’t check bounds at declaration time either:

type Foo<T: Sized> = [T];

is valid but results in a warning that the bound is not enforced and, since 2023, refers to ticket #112792 that aims to rectify the situation (the bullet point "Type aliases are checked for well-formedness." is relevant here in particular).

1 Like

[T] is not an "uninhabited type". It's a dynamically-sized type (DST) that has no compile time size; thus must always be behind some form of "reference". While both DSTs and uninhabited types have the property that one cannot create an instance of it directly, that doesn't make them the same idea.

Types likes ! despite being uninhabited have a known size; thus let x: !; is valid since the limitation on x is both that it has a valid type and that said type has a known size. [T] only satisfies the first property.

Yes, I already corrected myself, I'm aware of the distinction. The thing that confused me a bit (besides having a cold and not reading carefully) is that it's not the unsizedness of [_] that's the issue; the OP used a reference-to-unsized which should be fine. It's the [str] in particular that's ill-formed, even a reference to one, because slices and arrays naturally have Sized bound on the element type.

Fair, but I wanted to clarify that not only was your explanation not correct as it pertains to the issue—which you clearly retracted—but that the content within was also incorrect. In other words, one can say "I stand corrected" without that necessarily meaning the content was wrong but that it simply was not the reason for something. For example, why is 2 + 2 = 4? Answer: Because the sky is blue. Someone corrects me that it's due to some math stuff like ZFC. I respond "I stand corrected". The sky is still blue though. I was merely emphasizing the DST vs. uninhabited part which, to me at least, was not clear you retracted.

Yeah, that's the closest thing to an official issue. Type aliases don't have to be well-formed. There's a brief explanation on this lint.

The issue to fix(?) it are lazy type aliases (maybe(?)).

(This is sort of a "templates/fn-macros vs traits" situation IMO. I wonder how tenable it is to actually get rid of unchecked aliases.)

1 Like

Just think of type aliases as textual replacements, the compiler simply replaces every occurrence of Tp with the corresponding type. It doesn’t perform any checks or guarantees beyond verifying that it is a valid type.

1 Like
It's not quite a textual replacement, as mentioned in the lint footnotes...
  • "Shorthand" associated type paths

    // This works
    type ItemOf<I: IntoIterator> = I::Item;
    fn f(_: ItemOf<Vec<String>>) {}
    
    // But this fails as ambiguous
    fn g(_: <Vec<String>>::Item) {}
    
  • Affecting the default dyn lifetime

But that's probably the best way to think about it to get the gist of how they behave today.

Is this behaviour also expected for associated types of traits?

impl TestTrait for () {
    type Assoc = [str];
}

error[E0277]: the size for values of type `[str]` cannot be known at compilation time
 --> src/lib.rs:8:18
  |
8 |     type Assoc = [str];
  |                  ^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `[str]`
note: required by a bound in `TestTrait::Assoc`
 --> src/lib.rs:4:5
  |
4 |     type Assoc;
  |     ^^^^^^^^^^^ required by this bound in `TestTrait::Assoc`
help: consider relaxing the implicit `Sized` restriction
  |
4 |     type Assoc: ?Sized;
  |               ++++++++

and only when adding the suggested ?Sized bound it gives the correct error. Should i open an issue for this misleading diagnostic or is it a known limitation?

I see both errors without ?Sized.

1 Like

Oh you are right, sorry about that. I tested this in a playground with more compilation errors, so i missed the other one.

1 Like