Stream: t-lang/wg-unsafe-code-guidelines

Topic: loose notes on my mental model of initialization


Gankro (May 09 2019 at 21:09, on Zulip):

Was trying to work out how ManuallyDrop is supposed to actually be defined, wrote out some thoughts which might be interesting, or worth desensitizing me from, in the case that I need to write proper docs for these things: https://gist.github.com/Gankro/5a62f7b5a62e84b9c299f72d2ce9c33c

Nicole Mazzuca (May 09 2019 at 23:12, on Zulip):

seems reasonable, although I don't understand what the special case with local variables is, and I don't think double dropping on things that don't have drop glue is bad; basically, you should be able to do anything with a deinit variable, except for do anything logical with it's fields? maybe? I don't know if it's actually useful to have a non-library concept of "dropped"

Gankro (May 09 2019 at 23:14, on Zulip):

I needed to be careful that let x: bool; was not the same let x: bool = unintialized(), since the former is obviously fine, and the latter is insta-ub

Nicole Mazzuca (May 09 2019 at 23:14, on Zulip):

personally, I'm of the opinion from the top of my head that the destructor is simply a function you call

Gankro (May 09 2019 at 23:15, on Zulip):

But it has the library guarantee (that the language enforces where it can) that it's not called twice without reinitialization

Nicole Mazzuca (May 09 2019 at 23:15, on Zulip):

right

Nicole Mazzuca (May 09 2019 at 23:15, on Zulip):

so it's a library concept, not a language concept

Nicole Mazzuca (May 09 2019 at 23:16, on Zulip):

like, if you wanted to define a type for which double-drops are fine, and then call drop_in_place on it twice, that should be fine in my un-thought-through opinion

Nicole Mazzuca (May 09 2019 at 23:16, on Zulip):

i.e., let x = 0; drop_in_place(&mut x); should probably be fine

Gankro (May 09 2019 at 23:16, on Zulip):

Agreed

Gankro (May 09 2019 at 23:16, on Zulip):

Although it's not super clear why that is at all useful

Gankro (May 09 2019 at 23:16, on Zulip):

other than just "less ub"

Nicole Mazzuca (May 09 2019 at 23:16, on Zulip):

I mean, I think it's more like it's not useful to make it UB

Nicole Mazzuca (May 09 2019 at 23:17, on Zulip):

if you don't have any reason to make something UB, why complexify your model to make it UB?

Gankro (May 09 2019 at 23:18, on Zulip):

there's a world where the compiler could prove something on the basis that the opposite implies a double-drop

Nicole Mazzuca (May 09 2019 at 23:18, on Zulip):

I guess

Nicole Mazzuca (May 09 2019 at 23:18, on Zulip):

doesn't seem _that_ useful for opts tho

Gankro (May 09 2019 at 23:19, on Zulip):

no, but it wasn't obvious until I wrote it out that it's possible to make a consistent model where we don't just say it's UB

Gankro (May 09 2019 at 23:20, on Zulip):

as in, it might be if dropping created mem::uninit

RalfJ (May 10 2019 at 07:13, on Zulip):

hm, I dont think we want "deinit" on the lang level -- is that what you have been arguing about? (it seemed more about drop)

RalfJ (May 10 2019 at 07:15, on Zulip):

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.

I find this double-negation ("non-empty contraints") to be hard to understand -- what is even formally a constraint? the complement of the invariant?

RalfJ (May 10 2019 at 07:17, on Zulip):

your "logical constraints" seem to be "between" my validity/initialization invariant and my safety invariant. However, they all seem to be ptr-related, so they basically just refer to Stacked Borrows. I've been wondering if Stacked Borrows should "enter" ManuallyDrop.

RalfJ (May 10 2019 at 07:18, on Zulip):

however, this interpretation assumes that &mut T does not require the pointed-to memory to be a valid T, which some people are demanding

Gankro (May 10 2019 at 14:56, on Zulip):

oh I was assuming it also held for &mut T

Gankro (May 10 2019 at 15:14, on Zulip):

(My understanding is the Box and &mut T are "as if" they were T for most purposes)

gnzlbg (May 13 2019 at 10:46, on Zulip):

like, if you wanted to define a type for which double-drops are fine, and then call drop_in_place on it twice, that should be fine in my un-thought-through opinion

One consequence of saying that this is fine is that generic code cannot reason about these two different kind of types, so it has to assume that it can only drop things once.

gnzlbg (May 13 2019 at 10:46, on Zulip):

I had a similar question, where Drop::drop would panic, and the panic would be caught, whether you can try to re-drop a type.

gnzlbg (May 13 2019 at 10:47, on Zulip):

For example, when Drop::drop panics for an element of Vec, not all elements will be dropped, and their memory will be deallocated

gnzlbg (May 13 2019 at 10:48, on Zulip):

so trying to double drop the vector after the panic won't work

gnzlbg (May 13 2019 at 10:48, on Zulip):

the vector can't do anything about this, because even if it would correct, e.g., the len field, to leave the vector in a re-droppable state, the vector fields would be dropped, and the destructor of RawVec would deallocate the memory anyways

gnzlbg (May 13 2019 at 10:49, on Zulip):

fixing this would require making RawVec ManuallyDrop inside the vector, and correcting the length field when the element type implements drop. Feels like adding complexity to support something that shouldn't really happen in practice

RalfJ (May 13 2019 at 11:10, on Zulip):

oh I was assuming it also held for &mut T

it = what?

RalfJ (May 13 2019 at 11:11, on Zulip):

One consequence of saying that this is fine is that generic code cannot reason about these two different kind of types, so it has to assume that it can only drop things once.

we have that already without any kind of special treatment like "deinit" bits. Vec is a type where calling drop twice is UB.

RalfJ (May 13 2019 at 11:11, on Zulip):

For example, when Drop::drop panics for an element of Vec, not all elements will be dropped, and their memory will be deallocated

that's not true; Vec drops via the drop code for slices and that AFAIK properly resumes dropping with the next element.

gnzlbg (May 13 2019 at 11:29, on Zulip):

@RalfJ the update to the slice len will not be propagated to the vector len, the memory the slice refers to will be deallocated anyways, and the slice itself is dropped before the memory is deallocated, so I don't know how that matters

gnzlbg (May 13 2019 at 11:31, on Zulip):

we have that already without any kind of special treatment like "deinit" bits. Vec is a type where calling drop twice is UB.

Sure my point is that generic code (and users writing code in general) cannot assume that any type can be dropped more than once

gnzlbg (May 13 2019 at 11:33, on Zulip):

and in general, if drop fails, then the type must be leaked

RalfJ (May 13 2019 at 11:37, on Zulip):

RalfJ the update to the slice len will not be propagated to the vector len, the memory the slice refers to will be deallocated anyways, and the slice itself is dropped before the memory is deallocated, so I don't know how that matters

...?
You said "when Drop::drop panics for an element of Vec, not all elements will be dropped". I think that's wrong. When drop panics for an elem of Vec, it'll drop the next elem.

RalfJ (May 13 2019 at 11:38, on Zulip):

and in general, if drop fails, then the type must be leaked

you mean when working "from the outside"? sure, I can't just hope that drop is idempotent or so.

RalfJ (May 13 2019 at 11:39, on Zulip):

which is why I wonder why @Gankro has these "deinit" bits in their model, that seems like an unnecessary complication.

gnzlbg (May 13 2019 at 11:39, on Zulip):

@RalfJ indeed, you are right, slice drop continues dropping the next element

gnzlbg (May 13 2019 at 11:41, on Zulip):

making Drop::drop for Vec idempotent should be trivial then

gnzlbg (May 13 2019 at 11:46, on Zulip):

We could have an IdempotentDrop auto trait for that

gnzlbg (May 13 2019 at 11:46, on Zulip):

If being able to tell these apart ever has applcations

Gankro (May 13 2019 at 13:41, on Zulip):

@RalfJ well I do say at the top that "i don't think deinit is necessary"

Gankro (May 13 2019 at 13:41, on Zulip):

:)

Gankro (May 13 2019 at 13:42, on Zulip):

It's just kind've where I started when trying to picture ManuallyDrop, but later found it was seemingly unnecessary

Gankro (May 13 2019 at 13:42, on Zulip):

(similarly I thought it might be important to locals)

Gankro (May 13 2019 at 13:43, on Zulip):

@RalfJ whats the story for holding a raw ptr to a local? Is it UB if there's no other use keeping the var alive?

RalfJ (May 13 2019 at 14:16, on Zulip):

RalfJ well I do say at the top that "i don't think deinit is necessary"

fair^^ but I don't understand why they are even present optionally ;)

RalfJ (May 13 2019 at 14:16, on Zulip):

seems to be "historic reasons" then^^

RalfJ (May 13 2019 at 14:17, on Zulip):

RalfJ whats the story for holding a raw ptr to a local? Is it UB if there's no other use keeping the var alive?

using it becomes UB after a StorageDead

Gankro (May 13 2019 at 14:24, on Zulip):

@RalfJ is it sufficient to do a dead load at the end of the scope? e.g. let _keep_alive = val;

Gankro (May 13 2019 at 14:48, on Zulip):

hmm, miri seems to think this is fine? https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=690492a439a7a641b3ddb23be8d8f4fe

RalfJ (May 13 2019 at 14:49, on Zulip):

RalfJ is it sufficient to do a dead load at the end of the scope? e.g. let _keep_alive = val;

this depends on how rustc inserts StorageDead...

Gankro (May 13 2019 at 14:59, on Zulip):

@RalfJ what is the model that allows a variable to store a reference that dangles? Is that StorageDead?

RalfJ (May 13 2019 at 15:00, on Zulip):

I dont understand the question

RalfJ (May 13 2019 at 15:00, on Zulip):

StorageDead is an instruction, not a model

RalfJ (May 13 2019 at 15:00, on Zulip):

and Rust lets you store dangling ptrs in a variable in safe code, so all models allow that

Gankro (May 13 2019 at 15:34, on Zulip):

wait doesn't that contradict that they're dereferencable()

Gankro (May 13 2019 at 15:35, on Zulip):

or is that only function args

RalfJ (May 13 2019 at 16:00, on Zulip):

I am confused

RalfJ (May 13 2019 at 16:00, on Zulip):

could you give an example?

Gankro (May 13 2019 at 17:05, on Zulip):

@RalfJ something having type &T is supposed to indicate the compiler can freely read from it whever it wants, no? And a dangling reference is consequently UB?

RalfJ (May 13 2019 at 18:53, on Zulip):

@Gankro oh, sorry, missed the reference part. yeah references cannot dangle, though that does not answer the question of "when can they not dangle"

RalfJ (May 13 2019 at 18:53, on Zulip):

but that has nothing to do with StorageDead

Last update: Nov 19 2019 at 18:35UTC