0

I'm trying to implement a newtype wrapper around a borrowed value, in order to implement different behavior for a trait but can't get this to conform to the borrowing rules. I prepared a simplified example below.

Note: The actual code is more complex and also needs to work with values that need .as_any().downcast_ref() first, which is why I'd like this to work with borrowed values.

use std::cmp::Ordering;

trait OrdVec {
    fn cmp(&self, i: usize, j: usize) -> Ordering;
}

// note: actual trait bound is more complex and would lead to
// "conflicting implementations" when trying to implement `OrdSlice for &[f64]`
impl<T> OrdVec for Vec<T>
where
    T: Ord,
{
    fn cmp(&self, i: usize, j: usize) -> Ordering {
        self[i].cmp(&self[j])
    }
}

#[repr(transparent)]
struct F64VecAsOrdVec<'a>(&'a Vec<f64>);

impl OrdVec for F64VecAsOrdVec<'_> {
    fn cmp(&self, i: usize, j: usize) -> Ordering {
        let a: f64 = self.0[i];
        let b: f64 = self.0[j];

        if a < b {
            return Ordering::Less;
        }
        if a > b {
            return Ordering::Greater;
        }

        unimplemented!("convert NaN to canonical form and compare bitwise")
    }
}

fn as_ord_vec<'a>(first: bool, int_vec: &'a Vec<i64>, float_vec: &'a Vec<f64>) -> &'a dyn OrdVec {
    if first {
        int_vec
    } else {
        // returns a reference to data owned by the current function
        &F64VecAsOrdVec(float_vec)

        // newtype should have same layout but this fails on calling `cmp`
        // with "SIGSEGV: invalid memory reference"
        // let ord: &F64VecAsOrdVec = unsafe { std::mem::transmute(float_vec) };
        // ord
    }
}

#[test]
fn test_int_as_ord() {
    let int_vec = vec![1, 2, 3];
    let float_vec = vec![3.0, 2.0, 1.0];
    let int_ord = as_ord_vec(true, &int_vec, &float_vec);

    assert_eq!(Ordering::Less, int_ord.cmp(0, 1));
}

#[test]
fn test_float_as_ord() {
    let int_vec = vec![1, 2, 3];
    let float_vec = vec![3.0, 2.0, 1.0];
    let float_ord = as_ord_vec(false, &int_vec, &float_vec);

    assert_eq!(Ordering::Greater, float_ord.cmp(0, 1));
}
error[E0515]: cannot return reference to temporary value
  --> src/lib.rs:43:5
   |
43 |     &F64VecAsOrdVec(vec)
   |     ^-------------------
   |     ||
   |     |temporary value created here
   |     returns a reference to data owned by the current function
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Jörn Horstmann
  • 33,639
  • 11
  • 75
  • 118
  • @Shepmaster sure for that case it works, I should not have simplified the example too much. The real code needs to `match` and `downcast_ref` first and return the wrapper only in some cases. For other cases, the dowcasted reference already implements `OrdVec` and could be returned as is. – Jörn Horstmann Sep 01 '20 at 13:33
  • See also [How do I convert a Vec to a Vec without copying the vector?](https://stackoverflow.com/a/55081958/155423); [Deserialize a Vec> as Vec directly when Foobar has exactly one field](https://stackoverflow.com/a/58493026/155423); – Shepmaster Sep 01 '20 at 13:50
  • [The duplicates applied](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a621fc8169f6eae2b2b3a372f01a66f5). (And the [no-`unsafe` version](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1125311ce59f128a3554694a92d9fb64)) – Shepmaster Sep 01 '20 at 14:09
  • As an aside, I'd encourage using `match a.cmp(b)` instead of the sequential `<` and `>` checks. – Shepmaster Sep 01 '20 at 14:14
  • 1
    @Shepmaster thanks, I thought I tried both the `Box` and cast approach too but must have made some mistake at the time, seems to work so far. – Jörn Horstmann Sep 01 '20 at 15:07

0 Answers0