Skip to content

Instantly share code, notes, and snippets.

@Gankra
Last active May 9, 2019 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gankra/5a62f7b5a62e84b9c299f72d2ce9c33c to your computer and use it in GitHub Desktop.
Save Gankra/5a62f7b5a62e84b9c299f72d2ce9c33c to your computer and use it in GitHub Desktop.

My mental model of Initialization and Deinitialization:

This is kind've a brain dump of concepts, and does not necessarily represent a well-defined and minimized model. For instance, it's not clear to me if deinitialized memory is a "real" thing, or just a concept I like to have to think about dropped memory.

There are 3 (5) states a bit can have: uninit, init (0 or 1), deinit (0 or 1)

Freshly allocated memory is uninit. mem::unitialized() produces uninit.

Initializing memory makes it init with a definite value.

Memory becomes deinitialized if it is interpretted to have type T, and:

  • T: Drop, and it is dropped (ptr::drop_in_place)
  • T: !Copy, and it is moved (ptr::read/copy_from)
  • (?) the memory is never used again before being reinitialized (needed for NLL references?) (Drop is a use)

A type can have two classes of constraint:

  • Bitwise constraints (NonZero, Aligned, etc)
  • Logical constraints (Box/reference non-dangling (others?))

If we have a value: T, the compiler may assume all constraints are satisfied. If those constraints aren't satisfied, then Behaviour is Undefined.

If we have a value: ManuallyDrop<T>, it must satisfy bit constraints, but logical constraints need not be satisfied. Therefore: it may contain deinit versions of memory that was previously valid init.

If we have a value: MaybeUninitialized<T> it need-not satisfy any constraints. (may be init, deinit, or uninit)

If we have a value: T = uninit, the compiler may adversarially assume any bit patterns, which subsequently means that if T's bitwise or logical constraints are non-empty, Behaviour is Undefined.

If we have a value: T = deinit, the compiler may assume the bitwise constraints are satisfied but may adversarially assume the logical constraints are violated. (or is it that it must conservatively assume they don't hold?)

An interesting consequence of these constraints is the answer to the question of "is it Undefined Behaviour to double-Drop memory"? I believe the strict answer that falls out of this is yes for every type except Box. (Arguably references are also problematic but they are needs_drop() == false, which drop_in_place theoretically checks before promoting the raw pointer to a mutable reference. Perhaps only types which contain mutable references and themselves are needs_drop() == true are an issue?)

Since most interesting types with destructors do not actually have language-understood logical validity constraints (e.g. Vec, HashMap, etc), it is arguably valid at the language level to interpret deinit memory as those types and call drop_in_place. However for obvious sanity reasons, types must be allowed to assume that they aren't double-Dropped. As such, they must still be allowed to cause Undefined Behaviour if they're double-Dropped. But this is just a library concept, in the same vein as Send/Sync.

It should hypothetically be fine for a concrete type to implement Drop in a repeatable way, and for someone to manually drop it multiple times (say: one which just prints a debug message). In the Send/Sync metaphor, this would be akin to requiring T: Send but then actually working fine if T violates the contract of Send.

Special case: local variables.

A variable declaration let x: bool; does not actually allocate memory. Only upon assignment is the memory actually allocated, at which point the value is atomically initialized (actual atomics need-not be involved since one cannot take a pointer to the memory until it is initialized). Of course if the value is assigned mem::uninitialized() then it's atomically marked as uninit.

If a local would become deinitialized, it is deallocated instead. Unclear how this works: holding a raw pointer to a local that is never referenced by name again. (Specifically can imagine the compiler trying to reuse the memory for some other local...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment