-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Perhaps improve the documentation of drain members further #93769
Conversation
This comment has been minimized.
This comment has been minimized.
7bfe5c3
to
ee6fbc6
Compare
/// Creates a draining iterator that removes the specified range in the | ||
/// `VecDeque` and yields the removed items. | ||
/// Removes the specified range from the `VecDeque`, returning all removed | ||
/// elements as an iterator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind-of prefer the original sentence here. What’s the motivation for the change? The call to drain
itself does not do anything (yet) beyond creating an iterator. The returned iterator has a lifetime coupled to the mutable borrow; for an operation that “Removes the specified range … returning all removed elements as an iterator” one could expect an iterator that owns the elements and can be created with a shorter mutable borrow not coupled to a lifetime parameter of the type of the iterator.
The same applies to Vec
and String
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. I didn't realize that there's another unmerged PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I didn't realize anyone would notice this PR this month.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just happened to look at the list of latest/new open PRs today, (mainly because I have an open PR myself where I'm a bit impatiently checking whether anything has happened w.r.t. review yet,) then happend to be quickly looking into this one for whatever reason and probably wouldn't even have started any review if I hadn't spotted the typo of "closure". 😅 I hope you don't mind my lengthy side-discussion about validity of collection types that I kind-of opened in this thread 🙈
/// In case the iterator disappears without getting dropped (using | ||
/// [`core::mem::forget`], for example), the range remains in the string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t understand why this would need to be specified. It encourages usage of mem::forget
which usually shouldn’t be used on foreign types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or clarify what you mean with "this": the entire paragraph, or the constraint about what can happen if you leak an instance of this iterator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, this paragraph kind-of says to me "If you want to, during iteration, change your mind and keep the range inside of the original string, feel free to go ahead and just mem::forget
the iterator. This is a supported use-case and we guarantee the precise behavior in this case". I.e. it promises a certain behavior on mem::forget
ting the iterator, and thus (implicitly) encourages people to deliberately leak the iterator to achieve certain behavior. If we wanted to offer an actual documented and guaranteed way of avoiding the draining, then it should be via a (self
-consuming) method on the iterator, not by giving any promises on "what happens on mem::forget
IMO. (E.g. one could create a fn cancel(self)
on the string::Drain
iterator that explicitly doesn't remove anything from the String
, or vec::Drain
could provide a method that puts back the remaining elements into the original Vec
.)
All we should do in these kinds of documentation is say, very explicitly, something along the lines of "please don't ever skip dropping these iterators properly, it is considered a logic error and can, among other things, leave the original structure in an arbitrary unspecified (yet safe-to-use in terms of memory-safety) state with potentially surprising and unexpected behavior when you continue using it afterwards, it can result in arbitrary amounts of additional leaks of the to-be-drained items and/or other items in the same original data structure". I.e. strongly discourage actually leaking the iterators, and perhaps provide a non-exhaustive list of things that can happen upon leaking it anyways, without giving any promises that constrain the set of things that can happen any more than necessary by Rust's memory safety guarantees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my description.
Do you mean this following sentence?
Apparently people do use
mem::forget
a lot and are surprised by its effect, see the history of the notice onvec::drain
.
If "the history of the notice on vec::drain
" is supposed to explain this, can you perhaps provide some links for me to read up on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my description.
Do you mean this following sentence?
I mean primarily that the description already lists reasons why it would need to be specified as well as reasons not to.
Apparently people do use
mem::forget
a lot and are surprised by its effect, see the history of the notice onvec::drain
.If "the history of the notice on
vec::drain
" is supposed to explain this, can you perhaps provide some links for me to read up on this?
I went over #43244 again and don't see it. It's in the existence of #74652 and its predecessors introducing the paragraph. But I probably misinterperted #73844: it's the description that inspired the clarification, not someone actually using mem::forget and expecting to still look at the vector later. Some other posts too I'm sure, but probably less than I thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vec::Drain
could provide a method that puts back the remaining elements into the originalVec
.)
Intriguing idea, I think no one mentioned that possibility yet.
All we should do in these kinds of documentation is say, very explicitly, something along the lines of
I agree, but apparently no one wanted to discourage users in the Vec::drain description.
The wording here implicitly suggests that there’s a guarantee that e.g. For comparison, I don’t know how e.g. If we were to decide that an invalid state is allowed as an outcome from leaking a This relates to an interesting question I had a while ago and have not found the answer to yet: Does the Rust standard library guarantee that it’s impossible to (safely) create e.g. a E.g. unsafe code that gets passed in an arbitrary |
I thought this valid state is an implicit expectation or an explicit guarantee somewhere for any Rust class. Another one is that using mem::forget, elements are not cloned (even if they are If this is not a guarantee, then you're saying that the existing clause about mem::forget in vec::drain is in fact a guarantee, not a warning about how bad it can be? |
I don't understand that question. Obviously, by definition, safely creating a collection means it's valid? Even if the |
ee6fbc6
to
8fa032a
Compare
By valid state I mean thing like
Of course, these properties can be broken by a "bad" My question here would be whether such unsafe code could work with a |
Let me also individually address parts of your response.
Right, I'm not talking about a collection in an invalid state being able to violate any of Rust's safety guarantees. Yieled elements in a generic collection are gone when yielded, naturally. For
As explained in my previous comment, I mean a different notion of "valid" (related to avoiding what's currently documented as being a "logic error" in some places of the std docs) which is possible to violate in safe code. Even more, an invalid state under this notion, still usually should not be able to leak memory, AFAIK memory leaks in Rust are only supposed to happen when other things are already leaked (i.e. leaking one thing can result in more leakage) or by creating a reference-cycle in an Just to be sure this is clear, I'm mostly answering these questions because you asked, but this was just a side-note of an idea I had in my head once and I felt like writing down; it's not really all that relevant to this PR, me or you don't have to answer this "interesting question I had a while ago" of mine in this thread, it was just a side-note that is at most relevant for the question of whether or not to explicitly call leaking a drain-iterator a "logic error" (though any decision on whether or not to call it a "logic error" now shouldn't really prevent changing the documentation in this regard later). |
Yes, I agree.
Sure.
Yes, in the BTreeMap PRs I submitted it was made clear to me that no operation or panic may invalidate the tree. And now I do remember em::forget of the DrainFilter iteratior coming up. So now I realize that your assessment that "The wording here implicitly suggests that there’s a guarantee that e.g. BinaryHeap would still be in a valid state after a Drain iterator is leaked" is spot on: validity is a given. |
I don't think we have different notions of "valid" in the context of a well-behaved |
To summarize all above, I remember reading this would never be accepted. But I can't find it back. |
Would be really interested to learn more about this, maybe we should ask somewhere else, e.g. on Zulip.
Very sensible approach. In the discussion so far, I really only cared about "validity" in the context of a well-behaved If there really is a guarantee that you can, for well-behaved On the other hand, I’m not sure what how much really is gained from making creation of an invalid map unsafe/unsound. If construction of |
Indeed. Just trying to get github stuck in a loop… |
I’ve opened this thread: https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Validity.20guarantees.20of.20standard.20library.20data.20types.3F (or: read-only archive link) |
Building on #92902, let's try adding clauses about mem::forget in all of the drainy functions.
Reasons to do that:
&mut
. That makes a huge difference compared tointo_iter
.mem::forget
a lot and are surprised by its effect, see the history of the notice onvec::drain
.Reasons not to do that:
string::drain
.I'd rather move the Leaking section below Panics because it seems much more of a detail, just didn't do that to reduce changes where the clause exists already.
r? @yaahc