3

I have a Vec<MyType>. For my algorithm, I need MyType elements sorted according to different criteria. One is the insertion order, for which Vec<MyType> is used directly: I just index the element in the vector.

I also need to have MyType elements ordered by two other criteria.

In C++, I would simply create a std::set<size_t>, insert the indices of the elements in the Vec<MyType>, and provide a comparator object with a pointer/reference to the vector, so I could get the elements from the indices and compare them however I like.

According to this answer, I can't do that with Rust's BTreeSet, and I must implement trait Ord. But I can't implement Ord unless I also store a reference to Vec<MyType> in every element of the BTreeSet. I don't want to copy the elements because they are big and complex objects.

I also tried using Vec<Box<MyType>> and BTreeSet<&MyType>, but then I found that I can't have both inside the same struct, as a struct can't store a reference using its own lifetime.

So, is there a Rust idiomatic and safe way of doing this (preferably without using Rc<>, as I know my elements are never removed)?

struct MyType {
    /// Ordered by insertion order.
    basis: Vec<Box /* ?? maybe ?? */<MyType>>;

    /// Alternative ordering for basis.
    alt1_ordering: BTreeSet</*???*/>;

    /// Other alternative ordering for basis.
    alt2_orderign: BTreeSet</*???*/>;
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
lvella
  • 12,754
  • 11
  • 54
  • 106
  • This is similar to [How to define an ordered Map/Set with a runtime-defined comparator?](https://stackoverflow.com/questions/65533995/how-to-define-an-ordered-map-set-with-a-runtime-defined-comparator). I don't think you can do that, unfortunately. – Chayim Friedman Jun 06 '22 at 22:03
  • 1
    Does it need to remain sorted on each insertion, or can you populate the the `Vec` and then produce sorted indices later? – harmic Jun 07 '22 at 01:56
  • @harmic It has to stay sorted with each insertion. – lvella Jun 07 '22 at 13:17

1 Answers1

1

So, is there a Rust idiomatic and safe way of doing this

Yes!

preferably without using Rc<>

Oh.... well, no. But the overhead of Rc should be negligible, and it makes the implementation super straightforward. You already need a smart pointer type since the structure is recursive, anyway.

// Newtypes for implementing your alt1 and alt2 ordering.
struct Alt1Ordering(Rc<MyType>);
impl PartialEq for Alt1Ordering { ... }
impl PartialOrd for Alt1Ordering { ... }
impl Eq for Alt1Ordering {}
impl Ord for Alt1Ordering { ... }

struct Alt2Ordering(Rc<MyType>);
impl PartialEq for Alt2Ordering { ... }
impl PartialOrd for Alt2Ordering { ... }
impl Eq for Alt2Ordering {}
impl Ord for Alt2Ordering { ... }

struct MyType {
    /// Ordered by insertion order.
    basis: Vec<Rc<MyType>>,

    /// Alternative ordering for basis.
    alt1_ordering: BTreeSet<Alt1Ordering>,

    /// Other alternative ordering for basis.
    alt2_ordering: BTreeSet<Alt2Ordering>,
}

impl MyType {
    pub fn push(&mut self, value: MyType) {
        let value = Rc::new(value);
        
        self.basis.push(value.clone());
        self.alt1_ordering.insert(Alt1Ordering(value.clone()));
        self.alt2_ordering.insert(Alt2Ordering(value));
    }

    // Whatever accessors you need, e.g.:
    pub fn iter_basis(&self) -> impl Iterator<Item=&MyType> {
        self.basis.iter().map(|v| &**v)
    }
    
    pub fn iter_alt1(&self) -> impl Iterator<Item=&MyType> {
        self.alt1_ordering.iter().map(|v| &*v.0)
    }
    
    pub fn iter_alt2(&self) -> impl Iterator<Item=&MyType> {
        self.alt2_ordering.iter().map(|v| &*v.0)
    }
}

Note that MyType will not be Send nor Sync because it contains Rc. However, as long as all of the methods that clone or destroy an Rc take &mut self then it should be safe to implement Send and Sync, because if you have a &mut MyType then no other references should exist.

Note that code you didn't write can make the Send and Sync implementations unsound. For example, if you #[derive(Clone)], the compiler-generated implementation would make the trait implementations unsound since it would clone an Rc using a shared reference.

Alternatively, you can replace Rc with Arc to safely get Send and Sync without having to think about where you might clone/destroy Rcs.


It is theoretically possible to do this without Rc using pinning, but that requires unsafe, and I'd only investigate that route if the overhead of Rc proves to be significant (which I doubt will be the case).

cdhowie
  • 158,093
  • 24
  • 286
  • 300