-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Tracking Issue for BTreeMap cursors #107540
Comments
The key method on the Cursor and CursorMut types should be unsafe. If the key is interiorly mutable you could mutate it through the shared reference and break the invariants of BtreeMap. For example if the key is of type Cell<u32> I could call key.set and change the value from 1 to 1 billion and break potentially both the "Key must be unique" and "Key must be correctly ordered" invariants of BTreeMap. |
As I understand it, the BTreeMap invariants are not safety invariants. If the key is changed from underneath the map, it can lead to bugs. But crucially not to memory safety violations. The standard library docs make this distinction clear:
|
But the key_mut_unchecked method on CursorMut is unsafe because you might break the invariants of BTreeMap |
Unless there is a memory safety invariant with |
The invariant here is that if a function receives a |
You should clarify this in the docs. It's not really clear why key_mut_unchecked is unsafe reading the docs IMO |
FWIW, I agree that the docs are unclear. Copied directly from rust-lang/libs-team#141: /// Returns a mutable reference to the of the element that the cursor is
/// currently pointing to.
///
/// This can be used to modify the key, but you must ensure that the
/// `BTreeMap` invariants are maintained. Specifically:
///
/// * The key must remain unique within the tree.
/// * The key must remain in sorted order with regards to other elements in
/// the tree.
unsafe fn key_mut_unchecked(&mut self) -> Option<&mut K>; This does not explain what exactly is "unchecked". It appears to just repeat the invariants listed in the BTreeMap docs, which claim there is no possibility of UB by breaking them. |
So, I've been trying to create my own "range map" using this API and it feels… extremely clunky to me. Since I'm mostly working with the mutable API, I'll be talking about The handling of "ghost elements" is extremely clunky to me. From what I understand, these only can show up at the beginning and end of the map, since all entries into the API are via In that line of reasoning, I think that While I think that the guarantees for the |
There are several advantages in having a ghost element:
With that said, I can see how it can be hard to work with. I'd be happy to try out a better API if you want to sketch one out. |
I assume this is a non-starter for performance reasons, but the most "natural" feeling interface for me would be one that can point to all real elements and all points between/next to them (including start/end). There would then be convenience methods for skipping over whatever you're not interested in so you can stay in "element space" or "gap space" if you only care about one or the other most of the time. But again, I imagine this would make the API too slow because the compiler couldn't optimise away visiting all the gaps — does that sound right? (Edit: if this doesn't make sense, I'd be happy to sketch what I imagine would be the core API, and what would be the optional convenience layers.) (Edit2: or you could have separate cursor types for when you're pointing at an element vs a gap, and have the navigation functions consume self and convert between them where relevant. I think that would be on par for performance with the current design?) |
Honestly, the kind of modification I was thinking of was to leverage the existing |
I have been implementing a TCP segment reordering buffer (where you're storing ranges and they wrap around u32::MAX). So I have been looking into building a wrapping iterator over a BTreeMap and it seems that implementing a wrapping mutating cursor based on this is surprisingly nontrivial: you'd have to ... somehow ... swap between a &mut BTreeMap (to create a new CursorMut when you wrap) and a CursorMut and when you get rid of the CursorMut to replace it, put the &mut BTreeMap back. This is kind of nonobvious how to do when hand rolling a generator as one does while writing these things. I guess my feedback on this API is that it might need a function to move it back to the start or to a specified element. |
Can we document the potential hazard that In other words, when porting C++ to Rust,
FWIW, I'm strongly in favor of both API's 🚀 |
Are you sure about this? They should map the same way.
|
I'm more confused than I am sure, but I still get the impression that there is a hazard when porting C++ to Rust. C++ Rust So, here are the results on an example map:
My code#include #include int main () { std::map mymap; std::map::iterator itlow,itup; mymap[1]=1; mymap[2]=2; mymap[3]=3; mymap[4]=4; mymap[5]=5; itup=mymap.upper_bound (3); std::cout << itup->first << ' ' << itup->second << '\n'; itlow=mymap.lower_bound (3); std::cout << itlow->first << ' ' << itlow->second << '\n'; return 0; } #![feature(btree_cursors)] fn main() { use std::collections::BTreeMap; use std::ops::Bound; let mut a = BTreeMap::new(); a.insert("k1", "v1"); a.insert("k2", "v2"); a.insert("k3", "v3"); a.insert("k4", "v4"); a.insert("k5", "v5"); let cursor = a.upper_bound(Bound::Excluded(&"k3")); println!("upper exc {:?}", cursor.key()); let cursor = a.upper_bound(Bound::Included(&"k3")); println!("upper inc {:?}", cursor.key()); let cursor = a.lower_bound(Bound::Excluded(&"k3")); println!("lower exc {:?}", cursor.key()); let cursor = a.lower_bound(Bound::Included(&"k3")); println!("lower inc {:?}", cursor.key()); } @Amanieu thoughts? |
I see, you are indeed correct. Considering the feedback received so far, I am wondering if changing Cursors to represent a point between 2 elements rather than pointing at one element would be better:
Let me know if this sounds promising, and I'll try and sketch out a new API with this design. |
It's hard to say which is better without some short before & after code examples i.e. to do X, you do Anyway, after realizing the confusion, the API's were both intuitive and useful for the porting I was doing today and the only thing I'm asking for is for the docs to point out the porting hazard (alternatively, the function names could be changed to avoid matching those used in C++). |
Here's an API sketch for a cursor that resides at a point between 2 elements. Elements are read by "passing over" them with a cursor. impl<K, V> BTreeMap<K, V> {
fn lower_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
where
K: Borrow<Q> + Ord,
Q: Ord;
fn lower_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
where
K: Borrow<Q> + Ord,
Q: Ord;
fn upper_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
where
K: Borrow<Q> + Ord,
Q: Ord;
fn upper_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
where
K: Borrow<Q> + Ord,
Q: Ord;
}
struct Cursor<'a, K: 'a, V: 'a>;
impl<'a, K, V> Cursor<'a, K, V> {
fn next(&mut self) -> Option<(&'a K, &'a V)>;
fn prev(&mut self) -> Option<(&'a K, &'a V)>;
fn peek_next(&self) -> Option<(&'a K, &'a V)>;
fn peek_prev(&self) -> Option<(&'a K, &'a V)>;
}
struct CursorMut<'a, K: 'a, V: 'a>;
impl<'a, K, V> CursorMut<'a, K, V> {
fn next(&mut self) -> Option<(&K, &mut V)>;
fn prev(&mut self) -> Option<(&K, &mut V)>;
unsafe fn next_with_mut_key(&mut self) -> Option<(&mut K, &mut V)>;
unsafe fn prev_with_mut_key(&mut self) -> Option<(&mut K, &mut V)>;
fn peek_next(&self) -> Option<(&K, &V)>;
fn peek_prev(&self) -> Option<(&K, &V)>;
fn peek_next_mut(&mut self) -> Option<(&K, &mut V)>;
fn peek_prev_mut(&mut self) -> Option<(&K, &mut V)>;
unsafe fn peek_next_with_mut_key(&mut self) -> Option<(&mut K, &mut V)>;
unsafe fn peek_prev_with_mut_key(&mut self) -> Option<(&mut K, &mut V)>;
fn as_cursor(&self) -> Cursor<'_, K, V>;
// Inserts at current point, cursor is moved to before the inserted element.
fn insert_after(&mut self, key: K, value: V);
// Inserts at current point, cursor is moved to after the inserted element.
fn insert_before(&mut self, key: K, value: V);
unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
fn remove_next(&mut self) -> Option<(K, V)>;
fn remove_prev(&mut self) -> Option<(K, V)>;
} |
Conceptually, I think that I do like this API more, but I still think that the combinatorical explosion of options here could be more easily avoided by integrating properly with the Essentially, the position of the cursor between the elements is still unchanged. But rather than making the cursor this all-powerful entity, we give it equal footing with other various elements. To accomplish this, I propose a particular sleight of hand. We retain the meaning of a "gap" as the position in the map between elements. A vacant entry points to a particular gap, and an occupied entry points to a particular element and its adjacent gaps. When mutating a key for either kind of entry, you must uphold that the entry upholds the By leaving the semantics of the key specifically in the entry and requiring the entry for mutation of elements, the semantics of all the various mutable methods becomes more clear. Here's what the conceptual API would look like: type Position = /* we'll talk about this later */;
impl<'a, K, V> Cursor<'a, K, V> { // and `CursorMut`
// looks around the cursor
fn peek_next(&self) -> Option<(&K, &V)>;
fn peek_prev(&self) -> Option<(&K, &V)>;
// move the cursor
fn next(&mut self) -> Position;
fn prev(&mut self) -> Position;
}
impl<'a, K, V> CursorMut<'a, K, V> {
// attempts to insert at this spot in the cursor
fn insert(self, key: K) -> Result<VacantEntry<'a, K, V>, CursorMut<'a, K, V>>;
unsafe fn insert_unchecked(self, key: K) -> VacantEntry<'a, K, V>;
// mutates around the cursor
fn enter_next(&mut self) -> Option<OccupiedEntry<'_, K, V>>;
fn enter_prev(&mut self) -> Option<OccupiedEntry<'_, K, V>>;
}
impl<'a, K, V> OccupiedEntry<'a, K, V> { // and `VacantEntry` and `Entry` too
// requires upholding invariants mentioned
unsafe fn key_mut(&mut self) -> &mut K;
}
impl<'a, K, V> VacantEntry<'a, K, V> {
// if we want the ability to convert back into a cursor
fn seek_elsewhere(self) -> (K, CursorMut<'a, K, V>);
fn insert_and_seek_after(self, value: V) -> CursorMut<'a, K, V>;
fn insert_and_seek_before(self, value: V) -> CursorMut<'a, K, V>;
} Part of the reason why I think Note the bit I said about That's the bit I'm fuzziest on. But the rest is my basic idea on how to use entries to solve the complexity and the semantics. We don't need any weird |
A side note on the naming of |
While I'm not familiar enough with the cursor code to refactor everything to only point between elements, I did submit #112896 which adds the |
I'd like to also comment on the method names, if I can. I've participated in several programming competitions in C++, where binary searches and searches on binary trees are common. Every time I've had to perform one, I had to stare at the documentation for several minutes, struggling to figure out what Rust's current |
Rollup merge of rust-lang#118208 - Amanieu:btree_cursor2, r=dtolnay Rewrite the BTreeMap cursor API using gaps Tracking issue: rust-lang#107540 Currently, a `Cursor` points to a single element in the tree, and allows moving to the next or previous element while mutating the tree. However this was found to be confusing and hard to use. This PR completely refactors cursors to instead point to a gap between two elements in the tree. This eliminates the need for a "ghost" element that exists after the last element and before the first one. Additionally, `upper_bound` and `lower_bound` now have a much clearer meaning. The ability to mutate keys is also factored out into a separate `CursorMutKey` type which is unsafe to create. This makes the API easier to use since it avoids duplicated versions of each method with and without key mutation. API summary: ```rust impl<K, V> BTreeMap<K, V> { fn lower_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V> where K: Borrow<Q> + Ord, Q: Ord; fn lower_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V> where K: Borrow<Q> + Ord, Q: Ord; fn upper_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V> where K: Borrow<Q> + Ord, Q: Ord; fn upper_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V> where K: Borrow<Q> + Ord, Q: Ord; } struct Cursor<'a, K: 'a, V: 'a>; impl<'a, K, V> Cursor<'a, K, V> { fn next(&mut self) -> Option<(&'a K, &'a V)>; fn prev(&mut self) -> Option<(&'a K, &'a V)>; fn peek_next(&self) -> Option<(&'a K, &'a V)>; fn peek_prev(&self) -> Option<(&'a K, &'a V)>; } struct CursorMut<'a, K: 'a, V: 'a>; impl<'a, K, V> CursorMut<'a, K, V> { fn next(&mut self) -> Option<(&K, &mut V)>; fn prev(&mut self) -> Option<(&K, &mut V)>; fn peek_next(&mut self) -> Option<(&K, &mut V)>; fn peek_prev(&mut self) -> Option<(&K, &mut V)>; unsafe fn insert_after_unchecked(&mut self, key: K, value: V); unsafe fn insert_before_unchecked(&mut self, key: K, value: V); fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>; fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>; fn remove_next(&mut self) -> Option<(K, V)>; fn remove_prev(&mut self) -> Option<(K, V)>; fn as_cursor(&self) -> Cursor<'_, K, V>; unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>; } struct CursorMutKey<'a, K: 'a, V: 'a>; impl<'a, K, V> CursorMut<'a, K, V> { fn next(&mut self) -> Option<(&mut K, &mut V)>; fn prev(&mut self) -> Option<(&mut K, &mut V)>; fn peek_next(&mut self) -> Option<(&mut K, &mut V)>; fn peek_prev(&mut self) -> Option<(&mut K, &mut V)>; unsafe fn insert_after_unchecked(&mut self, key: K, value: V); unsafe fn insert_before_unchecked(&mut self, key: K, value: V); fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>; fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>; fn remove_next(&mut self) -> Option<(K, V)>; fn remove_prev(&mut self) -> Option<(K, V)>; fn as_cursor(&self) -> Cursor<'_, K, V>; unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>; } struct UnorderedKeyError; ```
The new commit causes an illegal instruction in [zack@laptop src]$ uname -a
Linux laptop 6.7.2-arch1-1 #1 SMP PREEMPT_DYNAMIC Fri, 26 Jan 2024 19:10:20 +0000 x86_64 GNU/Linux
[zack@laptop src]$ cargo version
cargo 1.77.0-nightly (7bb7b5395 2024-01-20)
[zack@laptop src]$ cargo run
Compiling foo v0.1.0 (/home/zack/projects/foo)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `/home/zack/projects/foo/target/debug/foo`
thread 'main' panicked at /rustc/635124704849eeead4e3a7bb6e663c5351571d93/library/alloc/src/collections/btree/navigate.rs:601:48:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Illegal instruction (core dumped) #![feature(btree_cursors)]
extern crate alloc;
use alloc::collections::BTreeMap;
use core::{cmp::Ordering, ops::Bound};
#[derive(Eq, PartialEq)]
struct ClosedInterval {
min: usize,
max: usize,
}
impl PartialOrd<Self> for ClosedInterval {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ClosedInterval {
/// The order is defined such that sets are equal to themselves.
/// A set is greater than all proper subsets.
/// Otherwise we simply order by `max`.
///
/// For example [2, 3] = [2, 3] < [1, 3] < [1, 4] < [0, 5] < [1, 6].
fn cmp(&self, other: &Self) -> Ordering {
match self.min.cmp(&other.min) {
Ordering::Less => {
if self.max >= other.max {
Ordering::Greater
} else {
Ordering::Less
}
}
Ordering::Equal => self.max.cmp(&other.max),
Ordering::Greater => {
if self.max > other.max {
Ordering::Greater
} else {
Ordering::Less
}
}
}
}
}
fn main() {
let mut map = BTreeMap::new();
insert(&mut map, ClosedInterval { min: 1, max: 4 });
insert(&mut map, ClosedInterval { min: 4, max: 6 });
}
fn insert(map: &mut BTreeMap<ClosedInterval, ()>, key: ClosedInterval) {
let mut cursor = map.lower_bound_mut(Bound::Included(&key));
if let Some(ge) = cursor.peek_next() {
if *ge.0 >= key {
return;
}
}
while let Some(lt) = cursor.prev() {
if key > *lt.0 {
cursor.remove_prev();
} else {
break;
}
}
// Causes an illegal instruction.
let _x = cursor.insert_after(key, ());
} |
Chalk me up as one of the "weirdos" that thought the old API made sense, lol. For example the new description for #![feature(btree_cursors)]
extern crate alloc;
use alloc::collections::BTreeMap;
use core::ops::Bound;
fn main() {
let mut map = BTreeMap::new();
map.insert(1i32, 1i32);
map.insert(2i32, 2i32);
map.insert(3i32, 3i32);
// Cursor should be "above" 2, shouldn't `peek_prev` return it?
assert!(map
.lower_bound(Bound::Included(&2i32))
.peek_prev()
.map_or(false, |tup| 1i32 == *tup.0));
// Cursor should be "above" 2, shouldn't `peek_next` return 3?
assert!(map
.lower_bound(Bound::Included(&2i32))
.peek_next()
.map_or(false, |tup| 2i32 == *tup.0));
} Similarly #![feature(btree_cursors)]
extern crate alloc;
use alloc::collections::BTreeMap;
use core::ops::Bound;
fn main() {
let mut map = BTreeMap::new();
map.insert(1i32, 1i32);
map.insert(2i32, 2i32);
map.insert(3i32, 3i32);
// Cursor should be "below" 2, shouldn't `peek_prev` return 1?
assert!(map
.upper_bound(Bound::Included(&2i32))
.peek_prev()
.map_or(false, |tup| 2i32 == *tup.0));
// Cursor should be "below" 2, shouldn't `peek_next` return it?
assert!(map
.upper_bound(Bound::Included(&2i32))
.peek_next()
.map_or(false, |tup| 3i32 == *tup.0));
} Because of my confusion, I'm struggling refactoring some code that used the old API. I have a specialized wrapper type, /// `(key, value)` is inserted iff there doesn't already
/// exist a `K` that is a superset of `key`.
/// In the event `(key, value)` will be inserted, all `(K, V)`s
/// where the `K` is a subset of `key` are first removed before
/// inserting.
pub fn insert(&mut self, key: K, value: V) -> bool {
// Since the only way to `insert` a `(K, V)` is via this function,
// the following is true:
// The smallest value greater than or equal to `key` will be a superset
// of `key` iff there exists a superset of `key`.
// This means that we simply have to check this one value to decide
// if `(key, value)` needs to be inserted; furthermore, in the event `(key, value)`
// does need to be inserted, we only have to traverse backwards until
// the beginning of `map` or a `K` is not a subset of `key`
// removing all `(K, V)`s where the `K` is a subset before we insert `(key, value)`.
let mut cursor = self.map.lower_bound_mut(Bound::Included(&key));
if let Some(ge) = cursor.key() {
if ge.is_superset(&key) {
return false;
}
}
cursor.move_prev();
while let Some(lt) = cursor.key() {
if key.is_proper_superset(lt) {
cursor.remove_current_and_move_back();
} else {
break;
}
}
// If `key` already existed in the map, then cursor
// would begin pointing at it. Since `Set::is_superset`
// returns true when both values are the same, we would
// immediately return from this function. This guarantees
// that `key` is unique before inserting.
// Since we only move to smaller values and each time we do
// we remove said value (or stop), the insert of `key` will occur after
// all smaller values and before larger values ensuring the sorted
// order of `map` is retained.
// Note that due to the requirements of `SetOrd`, a `K` that
// `is_proper_superset` of another `K` is also strictly greater.
cursor.insert_after(key, value).unwrap_or_else(
|_| panic!("there is a bug in how keys implement Ord or Set or there is a bug in BTreeMap")
);
true
} I tried re-writing this using the new API, but I don't get the correct result: pub fn insert(&mut self, key: K, value: V) -> bool {
let mut cursor = self.map.lower_bound_mut(Bound::Included(&key));
// Based on the perceived behavior of `lower_bound_mut` with an included bound,
// `peek_next` should give `key` iff it is exists; otherwise the first value strictly
// greater than it will be returned.
if let Some(ge) = cursor.peek_next() {
if ge.0.is_superset(&key) {
return false;
}
}
while let Some(lt) = cursor.prev() {
if key.is_proper_superset(lt.0) {
cursor.remove_next();
} else {
break;
}
}
cursor.insert_after(key, value).unwrap_or_else(
|_| panic!("there is a bug in how keys implement Ord or Set or there is a bug in BTreeMap")
);
true
} I'd appreciate any assistance. |
I've been using this for some performance optimization stuff at $WORK, and have been able to reap some significant performance improvements from it that I'm excited to be able to land when this stabilizes. A few observations from that experimentation:
|
Should be fixed by #120505. |
Same, but it is planned for this API to be added.
It was not obvious to me.
Thank you for this. That answers my plea for help. Specifically pub fn insert(&mut self, key: K, value: V) -> bool {
let mut cursor = self.map.lower_bound_mut(Bound::Included(&key));
if let Some(ge) = cursor.peek_next() {
if ge.0.is_superset(&key) {
return false;
}
}
loop {
match cursor.prev() {
None => break,
Some(lt) => {
if key.is_proper_superset(lt.0) {
cursor.remove_next();
} else {
cursor.next();
break;
}
}
}
}
cursor.insert_before(key, value).unwrap_or_else(|_| panic!("there is a bug in how SetOrd is implemented or a bug in BTreeMap"));
true
} which is not quite as nice as it was before: pub fn insert(&mut self, key: K, value: V) -> bool {
let mut cursor = self.map.lower_bound_mut(Bound::Included(&key));
if let Some(ge) = cursor.key() {
if ge.is_superset(&key) {
return false;
}
}
cursor.move_prev();
while let Some(lt) = cursor.key() {
if key.is_proper_superset(lt) {
cursor.remove_current_and_move_back();
} else {
break;
}
}
cursor.insert_after(key, value).unwrap_or_else(
|_| panic!("there is a bug in how keys implement Ord or Set or there is a bug in BTreeMap")
);
true
} |
I have the same problem. I was also confused by how bounds are interpreted. I think the term "above" is misleading. Finally, after quite a bit of time I came up with the following rules. let mut a = BTreeMap::new();
a.insert(1, "a");
a.insert(2, "b");
a.insert(3, "c");
a.insert(4, "d");
// a: [1, 2, 3, 4]
// The range of Included(&2) to Unbounded: [.. , 2 , ..]
// [2, +∞) ---------
let cursor = a.lower_bound(Bound::Included(&2));
// The actual cursor: [1 , 2 , 3 , ..]
// ^
assert_eq!(cursor.peek_prev(), Some((&1, &"a")));
assert_eq!(cursor.peek_next(), Some((&2, &"b")));
// The range of Excluded(&2) to Unbounded: [.. , 2 , ..]
// (2, +∞) -----
let cursor = a.lower_bound(Bound::Excluded(&2));
// The actual cursor: [1 , 2 , 3 , ..]
// ^
assert_eq!(cursor.peek_prev(), Some((&2, &"b")));
assert_eq!(cursor.peek_next(), Some((&3, &"c")));
// Conclusion: for lower_bound, the next node of the cursor is within the range.
// The range of Unbounded to Included(&3): [.. , 3 , ..]
// (-∞, 3] ---------
let cursor = a.upper_bound(Bound::Included(&3));
// The actual cursor: [.. , 2 , 3 , 4]
// ^
assert_eq!(cursor.peek_prev(), Some((&3, &"c")));
assert_eq!(cursor.peek_next(), Some((&4, &"d")));
// The range of Unbounded to Excluded(&3): [.. , 3 , ..]
// (-∞, 3) -----
let cursor = a.upper_bound(Bound::Excluded(&3));
// The actual cursor: [.. , 2 , 3 , 4]
// ^
assert_eq!(cursor.peek_prev(), Some((&2, &"b")));
assert_eq!(cursor.peek_next(), Some((&3, &"c")));
// Conclusion: for upper_bound, the previous node of the cursor is within the range. |
Feel free to submit a PR if you think you can make the documentation better. We can further refine the wording in that PR. |
I had a go at improving some of the documentation in #120936 |
improve `btree_cursors` functions documentation As suggested by `@Amanieu` (and others) in rust-lang#107540 (rust-lang#107540 (comment)) Improvements: - Document exact behavior of `{upper/lower}_bound{,_mut}` with each of the three `Bound` types using unambigous words `{greatest,greater,smallest,smaller,before,after}`. - Added another doc-example for the `Bound::Unbounded` for each of the methods - Changed doc-example to use From<[T; N]> rather than lots of `insert()`s which requires a mutable map which clutters the example when `mut` may not be required for the method (such as for `{upper,lower}_bound`. - Removed `# Panics` section from `insert_{before,after}` methods since they were changed to return an error instead a while ago. - Reworded some phrases to be more consistent with the more regular `BTreeMap` methods such as calling entries "key-value" rather than "element"s.
improve `btree_cursors` functions documentation As suggested by ``@Amanieu`` (and others) in rust-lang#107540 (rust-lang#107540 (comment)) Improvements: - Document exact behavior of `{upper/lower}_bound{,_mut}` with each of the three `Bound` types using unambigous words `{greatest,greater,smallest,smaller,before,after}`. - Added another doc-example for the `Bound::Unbounded` for each of the methods - Changed doc-example to use From<[T; N]> rather than lots of `insert()`s which requires a mutable map which clutters the example when `mut` may not be required for the method (such as for `{upper,lower}_bound`. - Removed `# Panics` section from `insert_{before,after}` methods since they were changed to return an error instead a while ago. - Reworded some phrases to be more consistent with the more regular `BTreeMap` methods such as calling entries "key-value" rather than "element"s.
Rollup merge of rust-lang#120936 - ripytide:master, r=Amanieu improve `btree_cursors` functions documentation As suggested by ``@Amanieu`` (and others) in rust-lang#107540 (rust-lang#107540 (comment)) Improvements: - Document exact behavior of `{upper/lower}_bound{,_mut}` with each of the three `Bound` types using unambigous words `{greatest,greater,smallest,smaller,before,after}`. - Added another doc-example for the `Bound::Unbounded` for each of the methods - Changed doc-example to use From<[T; N]> rather than lots of `insert()`s which requires a mutable map which clutters the example when `mut` may not be required for the method (such as for `{upper,lower}_bound`. - Removed `# Panics` section from `insert_{before,after}` methods since they were changed to return an error instead a while ago. - Reworded some phrases to be more consistent with the more regular `BTreeMap` methods such as calling entries "key-value" rather than "element"s.
improve `btree_cursors` functions documentation As suggested by ``@Amanieu`` (and others) in #107540 (rust-lang/rust#107540 (comment)) Improvements: - Document exact behavior of `{upper/lower}_bound{,_mut}` with each of the three `Bound` types using unambigous words `{greatest,greater,smallest,smaller,before,after}`. - Added another doc-example for the `Bound::Unbounded` for each of the methods - Changed doc-example to use From<[T; N]> rather than lots of `insert()`s which requires a mutable map which clutters the example when `mut` may not be required for the method (such as for `{upper,lower}_bound`. - Removed `# Panics` section from `insert_{before,after}` methods since they were changed to return an error instead a while ago. - Reworded some phrases to be more consistent with the more regular `BTreeMap` methods such as calling entries "key-value" rather than "element"s.
improve `btree_cursors` functions documentation As suggested by ``@Amanieu`` (and others) in #107540 (rust-lang/rust#107540 (comment)) Improvements: - Document exact behavior of `{upper/lower}_bound{,_mut}` with each of the three `Bound` types using unambigous words `{greatest,greater,smallest,smaller,before,after}`. - Added another doc-example for the `Bound::Unbounded` for each of the methods - Changed doc-example to use From<[T; N]> rather than lots of `insert()`s which requires a mutable map which clutters the example when `mut` may not be required for the method (such as for `{upper,lower}_bound`. - Removed `# Panics` section from `insert_{before,after}` methods since they were changed to return an error instead a while ago. - Reworded some phrases to be more consistent with the more regular `BTreeMap` methods such as calling entries "key-value" rather than "element"s.
… r=tgross35 Add missing periods on `BTreeMap` cursor `peek_next` docs Tracking issue: rust-lang#107540
Sorry, I did not read the entire discussion. Could anyone, please, briefly summarize why the decision was made to enable key mutation via a separate |
… r=tgross35 Add missing periods on `BTreeMap` cursor `peek_next` docs Tracking issue: rust-lang#107540
Rollup merge of rust-lang#128310 - kmicklas:btree-map-peek-next-docs, r=tgross35 Add missing periods on `BTreeMap` cursor `peek_next` docs Tracking issue: rust-lang#107540
Implement cursors for `BTreeSet` Tracking issue: rust-lang#107540 This is a straightforward wrapping of the map API, except that map's `CursorMut` does not make sense, because there is no value to mutate. Hence, map's `CursorMutKey` is wrapped here as just `CursorMut`, since it's unambiguous for sets and we don't normally speak of "keys". On the other hand, I can see some potential for confusion with `CursorMut` meaning different things in each module. I'm happy to take suggestions to improve that. r? `@Amanieu`
Implement cursors for `BTreeSet` Tracking issue: rust-lang#107540 This is a straightforward wrapping of the map API, except that map's `CursorMut` does not make sense, because there is no value to mutate. Hence, map's `CursorMutKey` is wrapped here as just `CursorMut`, since it's unambiguous for sets and we don't normally speak of "keys". On the other hand, I can see some potential for confusion with `CursorMut` meaning different things in each module. I'm happy to take suggestions to improve that. r? ``@Amanieu``
Implement cursors for `BTreeSet` Tracking issue: rust-lang#107540 This is a straightforward wrapping of the map API, except that map's `CursorMut` does not make sense, because there is no value to mutate. Hence, map's `CursorMutKey` is wrapped here as just `CursorMut`, since it's unambiguous for sets and we don't normally speak of "keys". On the other hand, I can see some potential for confusion with `CursorMut` meaning different things in each module. I'm happy to take suggestions to improve that. r? ```@Amanieu```
Implement cursors for `BTreeSet` Tracking issue: rust-lang#107540 This is a straightforward wrapping of the map API, except that map's `CursorMut` does not make sense, because there is no value to mutate. Hence, map's `CursorMutKey` is wrapped here as just `CursorMut`, since it's unambiguous for sets and we don't normally speak of "keys". On the other hand, I can see some potential for confusion with `CursorMut` meaning different things in each module. I'm happy to take suggestions to improve that. r? ````@Amanieu````
Rollup merge of rust-lang#128309 - kmicklas:btreeset-cursor, r=Amanieu Implement cursors for `BTreeSet` Tracking issue: rust-lang#107540 This is a straightforward wrapping of the map API, except that map's `CursorMut` does not make sense, because there is no value to mutate. Hence, map's `CursorMutKey` is wrapped here as just `CursorMut`, since it's unambiguous for sets and we don't normally speak of "keys". On the other hand, I can see some potential for confusion with `CursorMut` meaning different things in each module. I'm happy to take suggestions to improve that. r? ````@Amanieu````
@GrigorenkoPV according to the 8ee9693 commit message:
Maybe there is more discussion somewhere? I think one should explore a |
Feature gate:
#![feature(btree_cursors)]
ACP: rust-lang/libs-team#141
This is a tracking issue for the
Cursor
andCursorMut
types forBTreeMap
.A Cursor is like an iterator, except that it can freely seek back-and-forth, and can safely mutate the tree during iteration. This is because the lifetime of its yielded references is tied to its own lifetime, instead of just the underlying tree. A cursor either points to an element in the tree, or to a "ghost" non-element that is logically located after the last element and before the first element.
Public API
Steps / History
Unresolved Questions
Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: