rust-lang / reference

The Rust Reference
https://doc.rust-lang.org/nightly/reference/
Apache License 2.0
1.16k stars 452 forks source link

[type-layout] Document minimum size and alignment #1482

Open joshlf opened 2 months ago

joshlf commented 2 months ago

This is a draft implementation of https://github.com/rust-lang/rust/pull/117474#issuecomment-1989349525. I'm happy to make any suggested edits.

cc @RalfJung

RalfJung commented 1 month ago

There's a potential issue here -- not all types mentioned in the source code actually make it to codegen, so sometimes types that are "too big" do not lead to errors.

This example fails to build in a debug build but succeeds as a release build:

#![crate_type = "lib"]

pub fn f() {
    g::<u16>();
}
pub fn g<T>() -> std::mem::MaybeUninit<[T; 1 << 47]> {
    std::mem::MaybeUninit::uninit()
}
joshlf commented 3 weeks ago

There's a potential issue here -- not all types mentioned in the source code actually make it to codegen, so sometimes types that are "too big" do not lead to errors.

This example fails to build in a debug build but succeeds as a release build:

#![crate_type = "lib"]

pub fn f() {
    g::<u16>();
}
pub fn g<T>() -> std::mem::MaybeUninit<[T; 1 << 47]> {
    std::mem::MaybeUninit::uninit()
}

Good call. This shouldn't be a problem in our motivating use case, but obviously we need to be careful with what we promise.

Intuitively, the guarantee is something like "a type which you can do anything with" or "a type which you can create a pointer to". But I'm not sure how to formalize that idea.

For the motivating use case, we need to guarantee that this holds of *const T if I can actually execute code that synthesizes a *const T.

RalfJung commented 6 days ago

The new wording sounds good to me.

However, the issue with "which types actually end up being size-checked" remains. I don't have a good idea for how to express that.

@rust-lang/opsem @rust-lang/lang: any thoughts? There's tow questions; the easy one is what you think about the text of the PR, but the harder one is this concern. Specifically, the question is for which types we guarantee that their size is less than isize::MAX. You may think the answer is "all of them", but that's not true -- if a type gets optimized away before codegen, it may never be size-checked.

I don't have a good idea for how to even say which types are guaranteed to be size-checked in the current implementation. Similar to what we did for const-eval failures, we could try to have a notion of "required types" such that if a function runs, these types are definitely size-checked -- but given the huge number of types in a typical program, that sounds like it could be quite expensive.

joshlf commented 4 days ago

Is there some notion of "observability" we could use here? As in, if a type's existence is "observable" in some way? Intuitively, I'd imagine this just means that if the execution of a program in any way depends on the existence of the type (calling size_of::<T>(), constructing a *const T, etc), although I'm not sure if that notion is formalized anywhere.

RalfJung commented 4 days ago

The only things that are "observable" about a program are its IO behavior (and volatile accesses). That's the usual as-if rule. So I don't think that will help us here.