Skip to content

implement BTreeMap::merge#151981

Closed
asder8215 wants to merge 7 commits intorust-lang:mainfrom
asder8215:btreemap_append_with
Closed

implement BTreeMap::merge#151981
asder8215 wants to merge 7 commits intorust-lang:mainfrom
asder8215:btreemap_append_with

Conversation

@asder8215
Copy link
Contributor

@asder8215 asder8215 commented Feb 2, 2026

This PR implements BTreeMap::append_with() as mentioned in #147700 (and discussed by the forum post linked in the issue). I'm not sure if the issue made is an approval for this feature, but regardless I'm going to make an ACP soon. I decided to made a small change to the API for append_with than the one proposed by @nagisa:

pub fn append_with(&mut self, other: &mut BTreeMap<K, V, A>, conflict: impl FnMut(&K, V, V) -> V);

I think having the conflict function own self's value and returning an owned value might give a lot more flexibility to the user. It also feels a bit more intuitive to me as the user that a value of type V is being put into self when there's conflicting keys between self and other.

For example, if my values are String types and I want to combine my values when keys are conflicting, then they can either mutate selfs value with .push_str() passing in other's value and return self's value, or they can use format!() to return a concatenation of self's and other's value.

Now, a consequence for this approach is that it's very well possible that the user decides to return a value V that has nothing to do with self or other when it comes to a conflicting key. However, should we decide to do conflict: impl FnMut(&K, &mut V, V), I could have mutated self's value easily to produce a whole different value that has nothing to do with the original value of self or other.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Feb 2, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 2, 2026

r? @jhpratt

rustbot has assigned @jhpratt.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@jhpratt jhpratt added needs-acp This change is blocked on the author creating an ACP. S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. needs-acp This change is blocked on the author creating an ACP. labels Feb 3, 2026
@jhpratt
Copy link
Member

jhpratt commented Feb 3, 2026

Marked as waiting on ACP.

@asder8215
Copy link
Contributor Author

@rustbot label -S-waiting-on-review

Implemented the ACP changes with renaming function, changing signature, and changed the doc comments for merge a bit (it's similar to BTreeMap::append, but adds clarity to the callback function). Let me know if there's anything that I should modify or clarify in code/documentation!

@asder8215
Copy link
Contributor Author

Oops, I think it's:
@rustbot ready

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 5, 2026
@rust-log-analyzer

This comment has been minimized.

@asder8215
Copy link
Contributor Author

I don't think the CI issue is coming from my end since it's saying spurious network error?

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@asder8215 asder8215 force-pushed the btreemap_append_with branch from 3453de7 to 1b29082 Compare February 6, 2026 14:35
@jhpratt
Copy link
Member

jhpratt commented Feb 7, 2026

Stepping off rotation for a bit.

@rustbot reroll

@rustbot rustbot assigned Mark-Simulacrum and unassigned jhpratt Feb 7, 2026
@asder8215 asder8215 changed the title implement append_with() for BTreeMap implement BTreeMap::merge Feb 7, 2026
@Mark-Simulacrum Mark-Simulacrum removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 8, 2026
@asder8215
Copy link
Contributor Author

@rustbot ready

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 9, 2026
@asder8215
Copy link
Contributor Author

Note: I have a different implementation of BTreeMap::merge that uses CursorMut on self BTreeMap and IntoIter of other BTreeMap, which should result in it being more efficient than this current implementation because we don't need to take an IntoIter of self BTreeMap and reconstruct the merged and sorted BTreeMap from bottom up. It doesn't change the signature of the function, but it uses a lot of unsafe code as a result using CursorMut's methods and needing ownership of the value part from self CursorMut for the callback conflict function (the newer implementation also needs a lot more polish as well).

I think I'll put the optimized version in a separate PR because I know that this PR's code is safe (or safer) due to the implementation being a mirror of BTreeMap::append just with a callback conflict function. Also, if the unsafe optimized code is good for the implementation of BTreeMap::merge, then I might also make a PR on optimizing BTreeMap::append with CursorMut and IntoIter.

Let me know if this sounds good to you.

let mut b = BTreeMap::new();
for i in 5..$len {
b.insert(i, 2 * i);
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we add some comments on why certain constants were chosen here or what the intent of this code is? E.g., 5..$len is not immediately apparent when reading this to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the new PR, I mentioned a comment above macro_rules! create_merge_test {...} saying:

// On merging keys 5..8, the values are
// added together (i + 2 * i)

lhs.insert(IdBased { id: 0, name: "lhs_k".to_string() }, "1".to_string());
rhs.insert(IdBased { id: 0, name: "rhs_k".to_string() }, "2".to_string());

lhs.merge(rhs, |_, mut lhs_val, rhs_val| {
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a test / extend this one to verify that lhs_val and rhs_val are from left/right respectively?

Copy link
Contributor Author

@asder8215 asder8215 Feb 14, 2026

Choose a reason for hiding this comment

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

I hope I'm understanding correctly, but do you want me to add assertion cases in this merge callback function that verifies that lhs_val == "1", and rhs_val == "2"?

I could do that.

@@ -33,6 +33,36 @@ impl<K, V> Root<K, V> {
self.bulk_push(iter, length, alloc)
}

/// Merges all key-value pairs from the union of two ascending iterators,
/// incrementing a `length` variable along the way. The latter makes it
/// easier for the caller to avoid a leak when a drop handler panicks.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// easier for the caller to avoid a leak when a drop handler panicks.
/// easier for the caller to avoid a leak when a drop handler panics.

Copy link
Contributor Author

@asder8215 asder8215 Feb 14, 2026

Choose a reason for hiding this comment

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

In the new PR, I use CursorMut to help me merge the keys from other BTreeMap into self BTreeMap, so I don't have to consume the self BTreeMap reconstruct the merged BTreeMap from scratch.

/// If you want the tree to end up in a strictly ascending order, like for
/// a `BTreeMap`, both iterators should produce keys in strictly ascending
/// order, each greater than all keys in the tree, including any keys
/// already in the tree upon entry.
Copy link
Member

Choose a reason for hiding this comment

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

When we say "including any keys already in the tree", what keys are not in that set but relevant here?

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 copied the comment from the merge_from_sorted_iters function above, only making changes to the second paragraph I believe. However, looking at this comment, it's kinda already implied that a valid BTreeMap will be in strictly ascending/descending order if both BTreeMap follow the rule that their keys are in ascending/descending order. Not sure what the comment was getting at it here "including any keys already in the tree upon entry"


let self_iter = mem::replace(self, Self::new_in((*self.alloc).clone())).into_iter();
let other_iter = mem::replace(&mut other, Self::new_in((*self.alloc).clone())).into_iter();
let root = self.root.get_or_insert_with(|| Root::new((*self.alloc).clone()));
Copy link
Member

Choose a reason for hiding this comment

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

Why is this get_or_insert_with? Isn't the root guaranteed to be absent after the replace with a new tree above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest with you, I copied the code verbatim from BTreeMap::append(), which does this as well. The root does look to be absent though since we mem::replace() self with Self::new_in((*self.alloc).clone()).

The new PR doesn't use this approach though and preserves the content of self BTreeMap and inserting the other BTreeMap items in the right spots using CursorMut

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 14, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 14, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Feb 14, 2026
@asder8215
Copy link
Contributor Author

asder8215 commented Feb 14, 2026

@Mark-Simulacrum this PR will be closed in favor of #152418. I appreciate your time looking into this PR though!

@asder8215 asder8215 closed this Feb 14, 2026
@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. 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.

5 participants