rustsetb-tree

Rust BTreeSet insert unique values with duplicate ranking field


I need an ordered set so I'm using BTreeSet. I have an Item struct which contains a unique id, and a rank field which may be duplicated. I only want uniqueness to be considered for the id field and for it to be sorted on the rank field, so I've manually implemented PartialEq, PartialOrd, and Ord to do this, but it's not working as I expected

Here's the code:

use std::cmp::Ordering;
use std::collections::BTreeSet;

#[allow(dead_code)]
fn main() {
    let mut set = BTreeSet::new();
    set.insert(Item {
        id: 0,
        rank: 0,
    });
    set.insert(Item {
        id: 1,
        rank: 1,
    });
    set.insert(Item {
        id: 2,
        rank: 1,
    });
    for item in set.iter() {
        println!("{:?}", item);
    }

}

#[derive(Debug, Eq)]
struct Item {
    id: u32,
    rank: u32,
}

impl PartialEq for Item {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl PartialOrd for Item {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Item {
    fn cmp(&self, other: &Self) -> Ordering {
        self.rank.cmp(&other.rank)
    }
}

Playground

I get the output

Item { id: 0, rank: 0 }
Item { id: 1, rank: 1 }

When I expected

Item { id: 0, rank: 0 }
Item { id: 1, rank: 1 }
Item { id: 2, rank: 1 }

Why is this happening and how can I fix this?


Solution

  • This happens because you violated multiple of the contracts of PartialOrd:

    a == b if and only if partial_cmp(a, b) == Some(Equal)

    and

    If Ord is also implemented for Self and Rhs, it must also be consistent with partial_cmp

    But BTreeSet relies on those to hold.

    You can't implement your desired behavior with BTreeSet alone and should use a custom container instead.