BTreeMap::append was rewritten in Rust 1.96 to use a cursor-based merge
algorithm. The previous implementation
(append_from_sorted_iters, Rust ≤ 1.95) iterated both trees in order and
built a fresh tree; it never called lower_bound_mut so it did not assert
anything about the ordering.
Code
I tried this code:
use std::cmp::Ordering;
use std::collections::BTreeSet;
/// Minimal type with asymmetric Ord:
/// two distinct values both report `Less` relative to each other.
#[derive(Clone, Debug)]
struct Asym(i32);
impl PartialEq for Asym {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Asym {}
impl Ord for Asym {
fn cmp(&self, other: &Self) -> Ordering {
if self.0 == other.0 {
Ordering::Equal
} else {
// Both Asym(1) < Asym(2) and Asym(2) < Asym(1).
// This mirrors Value::Metadata's broken cross-value comparison.
Ordering::Less
}
}
}
impl PartialOrd for Asym {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn main() {
let mut a: BTreeSet<Asym> = BTreeSet::new();
a.insert(Asym(1));
let mut b: BTreeSet<Asym> = BTreeSet::new();
b.insert(Asym(2));
println!("Asym(1).cmp(Asym(2)) = {:?}", Asym(1).cmp(&Asym(2)));
println!("Asym(2).cmp(Asym(1)) = {:?}", Asym(2).cmp(&Asym(1)));
println!("Both Less — asymmetric Ord!");
println!("Calling BTreeSet::append on Rust {}...", std::env!("CARGO_PKG_RUST_VERSION", "unknown"));
println!("(panics on 1.96, succeeds on 1.95)");
// Rust 1.96 panics here:
// thread 'main' panicked at 'Cursor's peek_next should return None.'
// library/alloc/src/collections/btree/map.rs:1316
a.append(&mut b);
println!("Completed without panic.");
}
#[cfg(test)]
mod tests {
use super::*;
/// Minimal case: two single-element sets with asymmetric Ord.
/// Panics on 1.96: "Cursor's peek_next should return None."
#[test]
fn asymmetric_ord_triggers_unreachable() {
let mut a: BTreeSet<Asym> = BTreeSet::new();
a.insert(Asym(1));
let mut b: BTreeSet<Asym> = BTreeSet::new();
b.insert(Asym(2));
a.append(&mut b);
}
/// Larger sets — any combination with at least one cross-variant pair panics.
#[test]
fn asymmetric_ord_multi_element() {
let mut a: BTreeSet<Asym> = BTreeSet::new();
a.insert(Asym(0));
a.insert(Asym(1));
a.insert(Asym(3));
let mut b: BTreeSet<Asym> = BTreeSet::new();
b.insert(Asym(2));
b.insert(Asym(4));
a.append(&mut b);
}
}
I expected to see this happen: Value appended to BTreeSet
Instead, this happened: panic
Version it worked on
It most recently worked on: Rust 1.95
Version with regression
Rust 1.96
rustc --version --verbose:
rustc 1.96.0 (ac68faa20 2026-05-25)
binary: rustc
commit-hash: ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96
commit-date: 2026-05-25
host: x86_64-unknown-linux-gnu
release: 1.96.0
LLVM version: 22.1.2
Backtrace
Backtrace
@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged
BTreeMap::appendwas rewritten in Rust 1.96 to use a cursor-based mergealgorithm. The previous implementation
(
append_from_sorted_iters, Rust ≤ 1.95) iterated both trees in order andbuilt a fresh tree; it never called
lower_bound_mutso it did not assertanything about the ordering.
Code
I tried this code:
I expected to see this happen: Value appended to BTreeSet
Instead, this happened: panic
Version it worked on
It most recently worked on: Rust 1.95
Version with regression
Rust 1.96
rustc --version --verbose:Backtrace
Backtrace
@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged