Skip to content

panic in BTreeSet::append introduced in Rust 1.96 #157436

@izissise

Description

@izissise

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

<backtrace>

@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-collectionsArea: `std::collections`C-discussionCategory: Discussion or questions that doesn't represent real issues.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions