-
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
Add support for allocators in LinkedList
#103093
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @Mark-Simulacrum (or someone else) soon. Please see the contribution instructions for more information. |
This comment has been minimized.
This comment has been minimized.
I'm having trouble specializing on impl<I: IntoIterator, A: Allocator + Clone> SpecExtend<I> for LinkedList<I::Item, A> {
default fn spec_extend(&mut self, iter: I) {
iter.into_iter().for_each(move |elt| self.push_back(elt));
}
}
impl<T, A: Allocator + Clone> SpecExtend<LinkedList<T, A>> for LinkedList<T, A> {
fn spec_extend(&mut self, ref mut other: LinkedList<T, A>) {
self.append(other);
}
} The compiler doesn't like that |
} | ||
} | ||
} | ||
|
||
#[stable(feature = "rust1", since = "1.0.0")] | ||
impl<T> Default for LinkedList<T> { | ||
impl<T, A: Allocator + Clone + Default> Default for LinkedList<T, A> { |
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.
We usually just implement default for Global
instead of all allocators, because that would cause inference failures in many cases.
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.
Concretely, every program using LinkedList::default()
that doesn't explicitly assign the list to LinkedList<_>
(for example by passing it to a function or storing it in a field) will not infer an allocator, as type inference does not take into account default type parameters.
I don't know enough about specialization to say whether there's a better option there. It looks like similar impls for Vec avoid a direct I would recommend starting without this specialization, I think. @rustbot author |
0ef1a30
to
b0c0199
Compare
This comment has been minimized.
This comment has been minimized.
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
@rustbot label -S-waiting-on-author +S-waiting-on-review |
767c640
to
ed58eb5
Compare
}) | ||
} | ||
|
||
/// Adds the given node to the back of the list. | ||
#[inline] | ||
fn push_back_node(&mut self, mut node: Box<Node<T>>) { | ||
fn push_back_node(&mut self, node: Node<T>) { | ||
let mut node = Box::new_in(node, &self.alloc); |
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'm worried that changes like this may cause extra stack space etc to be used for moving the Node value around, since there's more places where we're passing it prior to sticking it in the Box. I'm not sure it's worth blocking over, but wanted to raise a note of caution.
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've thought of some ways to avoid moving the node onto the stack. Passing &mut self, mut node: Box<Node<T>, &A>
doesn't work because we can't mutably borrow the whole list while the box is borrowing the list's allocator. Unless we want to add a Clone
bound on the allocator, we'll have to change one of the arguments.
- The method doesn't mutate the allocator, so we could change the signature to take
head, tail, len
(or some struct containing them) instead of the whole list struct. - Alternatively, we could pass in a
NonNull<Node<T>>
instead of aBox
.
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.
@Mark-Simulacrum would the following signature be acceptable?
unsafe fn push_back_node(&mut self, node: Unique<Node<T>>)
I haven't found a way for this method to safely take a Box<Node<T>, &A>
, since:
- The box can't hold
&self.alloc
when the function already takes&mut self
- The box might use a different instance of
A
with a different memory pool
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.
It seems to me that the caller of this function can call Box::new_in, reaching into the LinkedList for the actual allocator, and then pass in node: Box<Node<T>, A>
just fine, right? Otherwise none of this would actually work.
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 I guess NonNull<Node<T>>
would have to be it. In any case, I'm inclined to not block on this particular function - we can figure that out at a later point. I think focusing on the other comments around changes in behavior is more important.
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.
Unless we want to add a Clone bound on the allocator,
FWIW, BTreeMap has such a bound to avoid &A
allocators -- those take up space in the Box even for the common case where A is a ZST. (I still think we should remove the blanked impl of &Allocator
to avoid people accidentally blowing up their boxes to twice their size... though in this case it's only temporaries within a function so it's not so bad.)
@@ -639,7 +679,7 @@ impl<T> LinkedList<T> { | |||
#[inline] | |||
#[stable(feature = "rust1", since = "1.0.0")] | |||
pub fn clear(&mut self) { | |||
*self = Self::new(); | |||
while self.pop_front_node().is_some() {} |
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 haven't looked at the Drop impl, but this seems less than optimal - previously we were presumably dropping the T in place rather than copying (moving it) out and then dropping it.
Note that this is really caused by the change to the pop function returning Node rather than the Box of a Node.
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.
This also means that we're losing the panic "continue dropping" behavior of the drop impl.
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.
Good point; I'll see if there's a way to avoid the excess copies.
Would it be reasonable to move the node drop code into clear
and then have the drop impl call clear
so they both get the panic guard?
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.
Seems plausible.
@rustbot author |
@Mark-Simulacrum, is it safe to assume two non-clone instances of an allocator type manage the same memory pool? If not, then we may not be able to keep O(1) time for Additionally: If multiple allocators of a type can have different pools, we might need unsafe versions of the private push/pop boxed node methods since the caller would have to guarantee the box and list allocators use the same pool. |
I'm not sure what you mean by non-clone instances of an allocator type - but in general I think the assumption that the same allocator type implies a common memory pool shouldn't be guaranteed. Most of the per-datastructure custom allocator work is pretty early days though. I don't think we would want to change append from O(1) to not O(1) in this PR; unfortunately, I'm not sure we can readily detect the situation where that optimization doesn't work with today's allocator API. It's probably a good idea to talk with folks working on it to see if maybe we need an cc @Amanieu -- I seem to recall you being involved with the allocator work |
I think this makes a good case for adding Regarding the For now, you can leave |
⌛ Testing commit c8d74c0feeb060dffcd70bce0c27e3255e07c527 with merge b6c88315c4c3e45ea9c10fef33ca21b1acdc2bce... |
💔 Test failed - checks-actions |
This comment has been minimized.
This comment has been minimized.
r=me with commits squashed (and please keep them squashed, no need for amendment commits in the future) |
c0e7995
to
34136ab
Compare
@bors r=Mark-Simulacrum |
@rytheo: 🔑 Insufficient privileges: Not in reviewers |
@bors r=Mark-Simulacrum |
☀️ Test successful - checks-actions |
Finished benchmarking commit (20d90b1): comparison URL. Overall result: no relevant changes - no action needed@rustbot label: -perf-regression Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)ResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesThis benchmark run did not return any relevant results for this metric. |
#[inline] | ||
fn push_front_node(&mut self, mut node: Box<Node<T>>) { | ||
unsafe fn push_front_node(&mut self, node: Unique<Node<T>>) { |
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.
Ah, we were actually quite happy that Unique
is basically unused in the standard library. It'd be better to avoid introducing new uses of this type until its status is clarified. Currently it makes absolutely no difference to NonNull
. There are some vague plans to maybe attach alias information to that type, but currently this does not happen. (Cc rust-lang/unsafe-code-guidelines#384)
Why was the type chosen here?
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.
Unique
seemed like a better fit since this method takes ownership of the node. Changing the signature to use NonNull
should be fine; this doc comment should probably also mention that the pointer shouldn’t be used again.
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.
Unique
is Copy
, it can't really indicate ownership.
…=cuviper Avoid using `ptr::Unique` in `LinkedList` code Addresses a [comment](rust-lang#103093 (comment)) by `@RalfJung` about avoiding use of `core::ptr::Unique` in the standard library.
Avoid using `ptr::Unique` in `LinkedList` code Addresses a [comment](rust-lang/rust#103093 (comment)) by `@RalfJung` about avoiding use of `core::ptr::Unique` in the standard library.
Avoid using `ptr::Unique` in `LinkedList` code Addresses a [comment](rust-lang/rust#103093 (comment)) by `@RalfJung` about avoiding use of `core::ptr::Unique` in the standard library.
Allows
LinkedList
to use a custom allocator