abi_stable_crates: lifetimes with R* types break compared to non R* types

Hi, sorry for the bad title, I’m not sure how to phrase the issue in a concise one-line description.

It seems that something inside abi_stables R* types breaks rusts lifetime tracking. We (@marioortizmanero and the rest of the tremor team) went through the attempt of using them for internal data as part of his PDK project and got to the point where a lot of lifetime errors (the nasty kind) were thrown up.

Initially, we suspected that we had done something wrong in the interpreter using the data so we tried to find the underlying cause and boiled it down to (hopefully) one initial issue that doesn’t require to go through 1000s of lines of code 😃

Basically, the following code

use abi_stable::std_types::RCow;
use std::borrow::Cow;

fn cmp_cow<'a, 'b>(left: &Cow<'a, ()>, right: &Cow<'b, ()>) -> bool {
    left == right
}

fn cmp_rcow<'a, 'b>(left: &RCow<'a, ()>, right: &RCow<'b, ()>) -> bool {
    left == right
}

fn main() {
    println!("Hello, world!");
}

fails with the following error:

    Checking repro v0.1.0 (/home/heinz/mario/repro)
error[E0623]: lifetime mismatch
 --> src/main.rs:9:10
  |
8 | fn cmp_rcow<'a, 'b>(left: &RCow<'a, ()>, right: &RCow<'b, ()>) -> bool {
  |                            ------------          ------------
  |                            |
  |                            these two types are declared with different lifetimes...
9 |     left == right
  |          ^^ ...but data from `left` flows into `right` here

For more information about this error, try `rustc --explain E0623`.
error: could not compile `repro` due to previous error

We could reproduce the same for RVec, and RHashMap as long they contained a lifetime.

Below a more extensive test with a number of other types:

use abi_stable::std_types::{RCow, RHashMap, RSlice, RStr, RString, RVec, Tuple2};
use std::{borrow::Cow, collections::HashMap};

fn cmp_string<'a, 'b>(left: &String, right: &String) -> bool {
    left == right
}
fn cmp_rstring<'a, 'b>(left: &RString, right: &RString) -> bool {
    left == right
}

fn cmp_str<'a, 'b>(left: &'a str, right: &'b str) -> bool {
    left == right
}
fn cmp_rstr<'a, 'b>(left: &RStr<'a>, right: &RStr<'b>) -> bool {
    left == right
}

fn cmp_vec<'a, 'b>(left: &Vec<&'a str>, right: &Vec<&'b str>) -> bool {
    left == right
}
fn cmp_rvec<'a, 'b>(left: &RVec<&'a str>, right: &RVec<&'b str>) -> bool {
    left == right
}

fn cmp_tpl<'a, 'b>(left: &(&'a str, u8), right: &(&'b str, u8)) -> bool {
    left == right
}
fn cmp_rtpl<'a, 'b>(left: &Tuple2<&'a str, u8>, right: &Tuple2<&'b str, u8>) -> bool {
    left == right
}

fn cmp_slice<'a, 'b>(left: &[&'a str], right: &[&'b str]) -> bool {
    left == right
}
fn cmp_rslice<'a, 'b>(left: &RSlice<&'a str>, right: &RSlice<&'b str>) -> bool {
    left == right
}
fn cmp_cow<'a, 'b>(left: &Cow<'a, ()>, right: &Cow<'b, ()>) -> bool {
    left == right
}
fn cmp_rcow<'a, 'b>(left: &RCow<'a, ()>, right: &RCow<'b, ()>) -> bool {
    left == right
}

fn cmp_hashmap<'a, 'b>(left: &HashMap<&'a str, ()>, right: &HashMap<&'a str, ()>) -> bool {
    left == right
}
fn cmp_rhashmap<'a, 'b>(left: &RHashMap<u8, &'a str>, right: &RHashMap<u8, &'b str>) -> bool {
    left == right
}

fn main() {
    println!("Hello, world!");
}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 17 (16 by maintainers)

Commits related to this issue

Most upvoted comments

This has finally been fixed and can be closed.

I’ve opened a new issue with the tasks that must be done: #81. This one is quite filled with discussion about how we figured out that the issue was variance, and it will be easier to track the progress in there. You can close this if you like, @rodrimati1992.

Oh my god, okay, I’m not crazy. I’ve been banging my head against the wall all day trying to fix this. I’ve tried literally everything. And I have some confirmation that it is indeed unsolvable in the case of Ord:

fn test<'a, 'b>(left: &RCow<'a, u8>, right: &RCow<'b, u8>) -> Ordering {
    left.cmp(right)
}

At some point I decided to give up and ask for help in Rust’s unofficial Discord server because they are absolute geniuses. And indeed it helped a lot: I was right that BorrowOwned was the issue, but I didn’t know the concept of “subtyping and variance”.

image

BorrowOwned makes it impossible to have comparisons with different lifetimes, because it makes the trait invariant:

impl<'a, B: ?Sized> Ord for RCow<'a, B>
where
    B: Ord + BorrowOwned<'a>,
{
    #[inline]
    fn cmp(&self, other: &RCow<'a, B>) -> Ordering {
        Ord::cmp(&**self, &**other)
    }
}

std’s implementation doesn’t bind Cow’s lifetime to any trait in its implementation of Ord, so it’s covariant:

impl<B: ?Sized> Ord for Cow<'_, B>
where
    B: Ord + ToOwned,
{
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        Ord::cmp(&**self, &**other)
    }
}

Not sure if you’re familiar with those terms; I certainly wasn’t. There’s more info here. I still don’t know much about it, so I can’t provide any solutions. For now, I can only think of:

  1. avoiding the comparison in the first place; or
  2. removing BorrowOwned and having people use RCow<RStr> (which isn’t that terrible); or
  3. waiting for GATs so that the lifetime isn’t binded to the trait, but to the RBorrowed type inside of it. I was told that GATs don’t solve the issue completely, though.

Just wanted to give a quick update about today’s suffering. I’ll give another update some other day.

Can confirm that the problem lies in the implementations of PartialEq for R* types. Will submit a PR soon.