rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.93k stars 1.57k forks source link

uninitialized/zeroed statics/consts #411

Closed Zoxc closed 5 years ago

Zoxc commented 10 years ago

There should be a way to create consts, statics and static muts which are partially or fully uninitialized or zeroed.

One way to do this could be to create built-in unsafe generic constants ZEROED and UNDEF.

@eddyb suggested to make the existing intrinsics usable in statics

A crude solution could be to allow the initializer in a static mut to be left out, which should cause zeroing all it's memory.

arielb1 commented 10 years ago

All-bits-zero isn't special in Rust (up to dynamic drops, which are being removed). Allowing some kind of mem::uninitialized() would be nice, through.

mahkoh commented 10 years ago

Sounds like CTFE. See https://github.com/rust-lang/rfcs/issues/322 for another proposal that suggested limited CTFE for intrinsic functions.

arielb1 commented 10 years ago

@mahkoh

We do have a limited version of CTFE. Currently it is limited to arithmetic and constructors. mem::uninitialized() won't increase its complexity so much .

However, adding size_of would complicate things more, because constants can be a part of type-checking, and this would make type-checking depend on LLVM. Because recursion in structs is so limited, this would still be tractable, but if we add traits etc. it would be a major mess.

I'm talking about stuff like:


trait Tr { fn doit(&self); }
impl Tr for [uint, ..std::mem::size_of::<uint>()] {
    fn doit(&self) { println!("called uint"); }
}
impl Tr for [uint, ..12-std::mem::size_of::<uint>()] {
    fn doit(&self) { println!("called 12-uint"); }
}

fn main() {
    let s = [0,1,2,3u];
    s.doit(); // which .doit is called depends on architecture
}

We could get around this by treating such values as "abstract values", so that they don't interfere with type-checking, and only expand them in trans. Of course this would create 2 kinds of constexprs, but otherwise we would be getting into what seems to me like a pretty deep dependent type hole.

C gets away with this because it "executes" declarations one-by-one.

eddyb commented 10 years ago

@arielb1 you can already do that, using uint::BYTES or uint::BITS.

arielb1 commented 10 years ago

uint was just a simple example, whose size is a well-known constant – you could put more complicated types there so we need to put trans::adt-s size calculation code into rustc.

eddyb commented 10 years ago

AFAIK, the size calculations are done by LLVM. We could have a LLVM context during type-checking, is there anything problematic with that? We should do that anyway instead of the hacks we've thrown around transmute.

arielb1 commented 10 years ago

@eddyb

They are done via LLVM and a big part of trans. I'll prefer keeping them separate. Actually, if we allow calling trait methods in constexprs then we'll have a total dependent type mess, and I strongly prefer not to go there, so we need to stop somewhere.

arielb1 commented 10 years ago

There is also the problem of struct -> constexpr recursion. Because there are only finitely many constants we can handle this with a DFS, but if we allow functions this could get more complicated.

eddyb commented 10 years ago

A big part of trans? I find it's quite isolated, compared to anything which deals with Block, for example.

if we allow calling trait methods in constexprs

Nobody said anything about that. You couldn't even use a size_of intrinsic or associated constant, with a non-concrete type parameter, as the length of an array (e.g. impl<T> Cast<T> for [u8, ..size_of::<T>()]) because the length has to be constant before monomorphization, though that may change at some point.

gnzlbg commented 6 years ago

Would it be possible to make mem::uninitialized a const fn ? (or mem::zeroed?)

oli-obk commented 6 years ago

@gnzlbg once https://github.com/rust-lang/rust/pull/46882 is merged, that is a trivial addition! (imo even uncontroversial, since you can write an unsafe const fn yourself that does the same thing via unions).

union Foo<T> {
    t: T,
    u: (),
}
unsafe const fn uninitialized<T>() -> T {
    Foo { u: () }.t
}
gnzlbg commented 6 years ago

@oli-obk congrats on the merge of that PR :)

Any news about this? This issue comes up every now and then in stdsimd: https://github.com/rust-lang-nursery/stdsimd/issues/403#issuecomment-376354681

oli-obk commented 6 years ago

My feeling is that someone should just open a PR and we FCP it, because you can do zeroed/uninitialized with horrible hacks in constants, so why not allow the nice obvious ways, too!?

Implementation guide: grep for "size_of" (yes with quotes) in rustc, grep for "uninitialized" in the miri repository, copy over code from the miri repository to rustc.

richard-uk1 commented 6 years ago

@oli-obk can you give some more hints? I can't find any code for "uninitialized" in miri that looks like the const eval stuff in librustc_mir/interpret/const_eval. Also, would I need to add uninitialized/zeroed to the intrinsics?

oli-obk commented 6 years ago

So... I've been told that these intrinsics should not exist due to various unsafety rules not playing nice with them.

Instead of creating a static/const with one of these intrinsics, your static/const should be of Union type with a zst variant that you use to "initialize" it.

I'm not sure this issue can be acted on. Maybe we should close it?

cc @RalfJung

RalfJung commented 6 years ago

Seems like essentially a duplicate of https://github.com/rust-lang/rust/pull/50150 to me.

Instead, we should strive to stabilize MaybeUninit and make it usable from const context.

richard-uk1 commented 6 years ago

I've now read the other issue & the MaybeUninit RFC and it does seem like this is a duplicate of the other issue. Maybe close this so no-one tries to implement it?

Centril commented 5 years ago

Closing as ~"completed".