Skip to content
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

Merged
merged 1 commit into from
Apr 25, 2023

Conversation

rytheo
Copy link
Contributor

@rytheo rytheo commented Oct 15, 2022

Allows LinkedList to use a custom allocator

@rustbot rustbot added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Oct 15, 2022
@rust-highfive
Copy link
Collaborator

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.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 15, 2022
@rust-log-analyzer

This comment has been minimized.

@rytheo
Copy link
Contributor Author

rytheo commented Oct 15, 2022

I'm having trouble specializing on SpecExtend. The problematic code is below:

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 A is repeated in the specializing impl. I could use advice on whether this is currently fixable.

}
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T> Default for LinkedList<T> {
impl<T, A: Allocator + Clone + Default> Default for LinkedList<T, A> {
Copy link
Member

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.

Copy link
Member

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.

@Mark-Simulacrum
Copy link
Member

Mark-Simulacrum commented Oct 16, 2022

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 Vec<T, A> impl, but I would need to do more digging to say more than that.

I would recommend starting without this specialization, I think.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 16, 2022
@rust-log-analyzer

This comment has been minimized.

@rytheo rytheo marked this pull request as ready for review October 18, 2022 02:29
@rustbot
Copy link
Collaborator

rustbot commented Oct 18, 2022

Hey! It looks like you've submitted a new PR for the library teams!

If this PR contains changes to any rust-lang/rust public library APIs then please comment with @rustbot label +T-libs-api -T-libs to tag it appropriately. If this PR contains changes to any unstable APIs please edit the PR description to add a link to the relevant API Change Proposal or create one if you haven't already. If you're unsure where your change falls no worries, just leave it as is and the reviewer will take a look and make a decision to forward on if necessary.

Examples of T-libs-api changes:

  • Stabilizing library features
  • Introducing insta-stable changes such as new implementations of existing stable traits on existing stable types
  • Introducing new or changing existing unstable library APIs (excluding permanently unstable features / features without a tracking issue)
  • Changing public documentation in ways that create new stability guarantees
  • Changing observable runtime behavior of library APIs

@rytheo
Copy link
Contributor Author

rytheo commented Oct 18, 2022

@rustbot label -S-waiting-on-author +S-waiting-on-review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 18, 2022
@rytheo rytheo requested a review from Nilstrieb October 19, 2022 01:19
})
}

/// 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);
Copy link
Member

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.

Copy link
Contributor Author

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 a Box.

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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() {}
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems plausible.

@Mark-Simulacrum
Copy link
Member

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 6, 2022
@rytheo
Copy link
Contributor Author

rytheo commented Nov 15, 2022

@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 append, since we'd have to loop and move all elements from one pool into another.

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.

@Mark-Simulacrum
Copy link
Member

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 fn is_same_as or something method...

cc @Amanieu -- I seem to recall you being involved with the allocator work

@Amanieu
Copy link
Member

Amanieu commented Dec 1, 2022

I think this makes a good case for adding trait Allocator: Eq so that you can check whether two allocators are compatible and can reuse each other's memory. I've opened a issue in wg-allocator for this: rust-lang/wg-allocators#109

Regarding the append function, I don't think we need to support an O(n) implementation: we can just panic if the 2 allocators are incompatible.

For now, you can leave append as only implemented for A = Global. The same applies to CursorMut::splice_*. Once Eq is added for allocators then these can be changed to be generic over A with an assertion that the allocators are the same.

@Mark-Simulacrum Mark-Simulacrum added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 4, 2023
@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 2, 2023
@bors
Copy link
Contributor

bors commented Apr 3, 2023

⌛ Testing commit c8d74c0feeb060dffcd70bce0c27e3255e07c527 with merge b6c88315c4c3e45ea9c10fef33ca21b1acdc2bce...

@bors
Copy link
Contributor

bors commented Apr 3, 2023

💔 Test failed - checks-actions

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Apr 3, 2023
@rust-log-analyzer

This comment has been minimized.

@Mark-Simulacrum
Copy link
Member

r=me with commits squashed (and please keep them squashed, no need for amendment commits in the future)

@rytheo
Copy link
Contributor Author

rytheo commented Apr 25, 2023

@bors r=Mark-Simulacrum

@bors
Copy link
Contributor

bors commented Apr 25, 2023

@rytheo: 🔑 Insufficient privileges: Not in reviewers

@ChrisDenton
Copy link
Contributor

@bors r=Mark-Simulacrum

@bors
Copy link
Contributor

bors commented Apr 25, 2023

📌 Commit 34136ab has been approved by Mark-Simulacrum

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 25, 2023
@bors
Copy link
Contributor

bors commented Apr 25, 2023

⌛ Testing commit 34136ab with merge 20d90b1...

@bors
Copy link
Contributor

bors commented Apr 25, 2023

☀️ Test successful - checks-actions
Approved by: Mark-Simulacrum
Pushing 20d90b1 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Apr 25, 2023
@bors bors merged commit 20d90b1 into rust-lang:master Apr 25, 2023
@rustbot rustbot added this to the 1.71.0 milestone Apr 25, 2023
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (20d90b1): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results

This 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.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
1.9% [1.9%, 1.9%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Cycles

This benchmark run did not return any relevant results for this metric.

@rytheo rytheo deleted the linked-list-alloc-api branch April 25, 2023 23:03
#[inline]
fn push_front_node(&mut self, mut node: Box<Node<T>>) {
unsafe fn push_front_node(&mut self, node: Unique<Node<T>>) {
Copy link
Member

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?

Copy link
Contributor Author

@rytheo rytheo Jul 20, 2023

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.

Copy link
Member

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.

compiler-errors added a commit to compiler-errors/rust that referenced this pull request Aug 11, 2023
…=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.
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Aug 12, 2023
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.
thomcc pushed a commit to tcdi/postgrestd that referenced this pull request Oct 17, 2023
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet