Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upborrow-checker allows partial reinit of struct that has been moved away, but no use of it. #21232
Comments
This comment has been minimized.
This comment has been minimized.
|
(however, unlike rust-lang/rfcs#533, it would not be all that terrible for non-zeroing dynamic-drop if we did not get around to making this illegal; we're already going to have to support representing structure fragments to implement non-zeroing dynamic drop, and initializations like the one in this ticket are just another case of that. Plus we might in the future add the ability to track such partial initializations and allow reads from initialized fields in partially-initialized structs.) |
kmcallister
added
the
A-typesystem
label
Jan 16, 2015
This comment has been minimized.
This comment has been minimized.
|
If this doesn't become an error, it should at least be a warning. Code like this is almost certainly a logic error of some kind, but currently compiles without error or warning: struct Foo { x: i32 }
let mut f = Foo { x: 0 };
drop(f);
f.x = 1; |
This comment has been minimized.
This comment has been minimized.
|
triage: P-backcompat-lang, 1.0 beta (but do see comments above noting that its not the end of the world if we did not fix this.) |
rust-highfive
added
the
P-backcompat-lang
label
Mar 3, 2015
This comment has been minimized.
This comment has been minimized.
|
triage: P-backcompat-lang (1.0 beta) just trying to accommodate highfive... |
rust-highfive
added
P-backcompat-lang
and removed
P-backcompat-lang
labels
Mar 3, 2015
rust-highfive
added this to the 1.0 beta milestone
Mar 3, 2015
panicbit
referenced this issue
Mar 3, 2015
Merged
Move to new io/fs/path API and adjust to Hyper changes #302
This comment has been minimized.
This comment has been minimized.
|
Interesting, I see the bug being that you get an error at all -- that is, I think that once you have fully "reinitialized" the structure, you should be able to read it again. (Rather than the ability to partially reinit the struct as being an error.) |
This comment has been minimized.
This comment has been minimized.
|
I'm going to tag this as I-Needs-Decision. |
nikomatsakis
added
the
I-needs-decision
label
Mar 5, 2015
This comment has been minimized.
This comment has been minimized.
|
Note that this is currently an error if the moved value has a destructor. For example, if you add struct Foo { x: i32 }
impl Drop for Foo { fn drop(&mut self) {} }
let mut f = Foo { x: 0 };
drop(f);
f.x = 1;then it is rejected with "error: partial reinitialization of uninitialized structure |
This comment has been minimized.
This comment has been minimized.
|
at this point I think I would be in favor saying that we can live with the current semantics as is today, and fix things in the future backwards-compatibly to either 1. track the initialized parts and allow reading from them, as I mentioned at the end of my comment, or 2. the slightly more limited (but perhaps more in line with our current compiler infrastructure) approach of tracking when the entire (non-Drop) structure is reinitialized and then allow reads from it, as outlined in niko's comment. So, bascially, I'm willing to reclassify this as a P-low, not 1.0 bug. |
This comment has been minimized.
This comment has been minimized.
|
triage: P-low () |
rust-highfive
added
P-low
and removed
P-backcompat-lang
labels
Mar 5, 2015
This comment has been minimized.
This comment has been minimized.
|
(apparently empty brackets does not clears the milestone, though they may have at one point?) |
pnkfelix
removed this from the 1.0 beta milestone
Mar 5, 2015
This comment has been minimized.
This comment has been minimized.
|
@mbrubeck also, if you wanted to try to develop a lint to warn about this case, I would not object (assuming it did not have too many false positives). |
This comment has been minimized.
This comment has been minimized.
|
@mbrubeck yes, we are more restrictive about structs with destructors, because in that case it doesn't make sense to drop one part and not another. |
This comment has been minimized.
This comment has been minimized.
logannc
commented
Jun 30, 2015
|
Real quick question after seeing this on Reddit: When we are "writing into the uninitialized memory that resulted from moving p into the closure", where does that written value go? Does the compiler actually emit write instructions? If so, could that lead to a vulnerability by overwriting an important field? (I would check the LLVM output myself, but I wouldn't have a clue how to read it.) |
This comment has been minimized.
This comment has been minimized.
|
It will generate writes to |
This comment has been minimized.
This comment has been minimized.
|
@logannc If one omits the assignment to fn main() {
let mut a = A{val: 1};
let a_ = a;
/// a.val = 2; // In the original example this line is uncommented
println!("a_: {}", a_.val);
/// println!("a: {}", a.val); // error: use of moved value: `a.val` ?!?!
}the optimizer should omit the stack slot of fn main() {
let mut a : A; // uninitialized
let a_ = A{val: 1};
a.val = 2; // Initialize a here
println!("a_: {}", a_.val);
/// println!("a: {}", a.val); // error: use of possibly uninitialized variable
}However, I do not understand why using a reinitialized variable results in an error, e.g. by uncommenting the lines in the above examples. It seems that writing to uninitialized memory:
This makes no sense: either one cannot write to uninitialized memory, or doing so reinitializes the variable so that it can be used. Being able to write to uninitialized memory but not being able to use that value is weird. |
This comment has been minimized.
This comment has been minimized.
|
Triage: updated code
Same error happens. |
pnkfelix
added
the
T-lang
label
Jan 11, 2016
This comment has been minimized.
This comment has been minimized.
To be clear, you can already do that: struct S {
x: u32,
y: u32,
}
fn main() {
let s: S;
if true {
s = S { x: 1, y: 2 };
} else {
s = S { x: 2, y: 1 };
}
}I wouldn't say it's idiomatic, but it's possible. |
This comment has been minimized.
This comment has been minimized.
|
I think the one that's particularly bad is this one: fn main() { let mut s: S; s.x = 10; use(s.x); }I don't think it should ever be allowed to write something that you cannot then read. I would be fine with either direction of a fix for that, whether denying the write or allowing the read. Edit: I do think that, as long as the following is valid, there should be a lint about the assignment, which there currently isn't: struct S { x:i32, _y:i32 }
fn main() { let mut s: S; s.x = 10; } |
This comment has been minimized.
This comment has been minimized.
|
Discussed at lang team meeting. For reference, here are the three example programs we discused (assuming we're starting with e.g.
There was generally strong agreement that in the long term, it would be good to accept all three cases. I.e., we should allow building up a record in a piecemeal fashion, and it would also be good to allow reading a field after one has written to it, regardless of the original initialization state of the struct overall.
There was some lukewarm support that, in the short term, we should be rejecting all three cases in the name of consistency.
@nikomatsakis hypothesized during the meeting that implementing this would be perhaps a day's worth of thought and/or programming effort. With that in mind, we have tentatively decided to attempt, for the short-term, to adopt the semantics that rejects all three cases. (I.e., it will become an error to write to a field of a struct if that whole struct has not be already initialized) |
pnkfelix
added
P-high
I-nominated
and removed
I-needs-decision
T-lang
P-low
I-nominated
labels
Oct 4, 2018
This comment has been minimized.
This comment has been minimized.
|
the effort here, i.e. the new check described in my previous comment, needs to happen soon if its going to happen at all. So I'm retagging this as |
This comment has been minimized.
This comment has been minimized.
|
Assigning to @spastorino; they are going to drive the initial effort on making it an error, for the short-term, to write to a field of a struct if that whole struct has not be already initialized. Once that is done, we should either leave this issue open and assign it to someone working on the longer term project for supporting partial-assignments of uninitialized records, or close this issue and open a fresh issue to track the longer term project. |
pnkfelix
assigned
spastorino
Oct 8, 2018
This comment has been minimized.
This comment has been minimized.
|
Putting on the RC2 milestone. If we're doing this change at all, its gotta land by then IMO. |
pnkfelix
added this to the Edition 2018 RC 2 milestone
Oct 9, 2018
nikomatsakis
referenced this issue
Oct 9, 2018
Closed
Fix variable does not need to be mutable warning #54621
This was referenced Oct 11, 2018
This comment has been minimized.
This comment has been minimized.
pnkfelix
referenced this issue
Oct 15, 2018
Merged
reject partial init and reinit of uninitialized data #54941
pnkfelix
added a commit
to pnkfelix/rust
that referenced
this issue
Oct 16, 2018
This comment has been minimized.
This comment has been minimized.
|
(We did talk about this at the last meeting; removing the I-nominated tag.) |
pnkfelix commentedJan 16, 2015
Here is some sample code:
This compiles without an error; running it prints
f: 2u8.If you uncomment the last line, you get an error saying "error: use of moved value:
p". Likewise if you just attempt to print individual fields.While I do see the logic being used here ("we are not overwriting the value within the closure itself; we are writing into the uninitialized memory that resulted from moving
pinto the closure"), it seems potentially confusing.Is there any reason that we allowing these writes, which cannot be subsequently read? It seems like a situation analogous to moving into a moved-away array (see rust-lang/rfcs#533 )