-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lifetimes with R* types break compared to non R* types #75
Comments
It's worth noting that this only happens with use abi_stable::std_types::{RCow, RHashMap};
use std::{borrow::Cow, collections::HashMap};
use beef::Cow as BCow;
use halfbrown::HashMap as HHashMap;
fn cmp_cow<'a, 'b>(left: &Cow<'a, str>, right: &Cow<'b, str>) -> bool {
left == right
}
fn cmp_bcow<'a, 'b>(left: &BCow<'a, str>, right: &BCow<'b, str>) -> bool {
left == right
}
fn cmp_rcow<'a, 'b>(left: &RCow<'a, str>, right: &RCow<'b, str>) -> bool {
left == right
}
fn cmp_hashmap<'a, 'b>(left: &HashMap<u8, &'a str>, right: &HashMap<u8, &'b str>) -> bool {
left == right
}
fn cmp_hhashmap<'a, 'b>(left: &HHashMap<u8, &'a str>, right: &HHashMap<u8, &'b str>) -> bool {
left == right
}
fn cmp_rhashmap<'a, 'b>(left: &RHashMap<u8, &'a str>, right: &RHashMap<u8, &'b str>) -> bool {
left == right
} Currently investigating if it's just the
Or for
|
Can confirm that the problem lies in the implementations of |
Ok, so note that while I've fixed the snippet of code in this issue (which showcases use abi_stable::std_types::{RCow, RHashMap, RSlice, RStr, RString, RVec, Tuple2};
use std::{borrow::Cow, collections::HashMap, cmp::Ordering};
fn cmp_cow<'a, 'b>(left: &Cow<'a, ()>, right: &Cow<'b, ()>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rcow<'a, 'b>(left: &RCow<'a, ()>, right: &RCow<'b, ()>) -> Option<Ordering> {
left.partial_cmp(right)
} Which is strange, because the signatures are actually the same:
Interestingly enough, So it might be a case of compiler magic? Same for fn cmp_cow<'a, 'b>(left: &Cow<'a, str>, right: &Cow<'b, str>) -> Ordering {
left.cmp(right)
}
fn cmp_rcow<'a, 'b>(left: &RCow<'a, str>, right: &RCow<'b, str>) -> Ordering {
left.cmp(right)
}
fn cmp_bcow<'a, 'b>(left: &beef::Cow<'a, str>, right: &beef::Cow<'b, str>) -> Ordering {
left.cmp(right)
} Maybe it has to do with Edit: oh, yup. It's |
The lifetime's required to make the associated types in the three |
Nevermind, sorry, I was trying to figure out how So far I've narrowed it down to this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b752600ddd4b1e03e2fea2fa5fc1228b |
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 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
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)
}
}
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:
Just wanted to give a quick update about today's suffering. I'll give another update some other day. |
Even after understanding in detail why this happens, there aren't any solutions other than the ones I mentioned. In fact, there are less possible approaches because I'm now certain that GATs won't work. I followed quite a few tutorials and guides about subtyping and variance and was able to more or less understand the topic. The problem was that none of these had an example equivalent to I first gave it a try with GATs: what if we binded the lifetime to the associated type instead of to the trait itself? To me the problem first seemed to be that we were introducing the lifetime I'm not an expert in GATs because I haven't used them much, but I was finally able to get the following (simplified) interface working: impl<T> BorrowOwned for T {
type RBorrowed<'a> where T: 'a = &'a T;
}
However, as I was warned, the interface itself compiled, but the lifetime issues were still there. I had now seen with my own eyes that the problem was invariance. Afterwards, I finally found a reference as to why this happens for our specific case: https://rustc-dev-guide.rust-lang.org/variance.html#variance-and-associated-types The problem is that it's documented on the Rust developer book, and not in the Rustnomicon or the reference. Only compiler developers are meant to check that book. And it makes sense, because I don't really understand the explanation; it's stuff related to how the compiler works. Anyway, it simply says that "traits with associated types must be invariant with respect to all of their inputs". There is one workaround, and that's using fn compare<'a, 'b>(left: &RCow<'a, str>, right: &RCow<'b, str>) -> Ordering {
unsafe {
let right: &RCow<'a, str> = std::mem::transmute(right);
left.cmp(right)
}
} How can we be sure that the Note, however, that you can't improve the ergonomics by creating a wrapper of Here, struct SCow<'a>(RCow<'a, ()>); We would have to hide You can find a playground link with the @rodrimati1992, do you think removing Downsides:
Upsides:
P.S. Sorry if this is overly verbose. I wanted to explain everything to make sure it's correct and so that my position is understood clearly. I will release a full writeup of this for https://nullderef.com/ whenever I find time because I find it to be a pretty intersting topic, but for now this should have enough details to get the issue moving forward. |
I'll mention that if you're redesigning
|
After trying several new designs for
Solution: Having two
|
Here's an alternate approach which takes the Code
use abi_stable::std_types::{RSlice, RVec, RStr, RString};
use std::borrow::Borrow;
use std::cmp::{PartialEq, PartialOrd, Ord, Ordering};
use std::ops::Deref;
///////////////////////////////////////////////////////////////////////
trait QueryOwnedType: Deref {
type Owned: Borrow<Self::Target>;
}
trait IntoOwned: Copy + QueryOwnedType {
fn into_owned(self) -> Self::Owned;
}
impl<T> QueryOwnedType for &T {
type Owned = T;
}
impl<T: Clone> IntoOwned for &T {
fn into_owned(self) -> T {
self.clone()
}
}
impl QueryOwnedType for RStr<'_> {
type Owned = RString;
}
impl IntoOwned for RStr<'_> {
fn into_owned(self) -> RString {
self.into()
}
}
impl<T> QueryOwnedType for RSlice<'_, T> {
type Owned = RVec<T>;
}
impl<T: Clone> IntoOwned for RSlice<'_, T> {
fn into_owned(self) -> RVec<T> {
self.to_rvec()
}
}
///////////////////////////////////////////////////////////////////////
#[derive(Debug)]
enum RCow<B, O>{
Borrowed(B),
Owned(O),
}
type BCow<'a, T> = RCow<&'a T, T>;
type RCowStr<'a> = RCow<RStr<'a>, RString>;
type RCowSlice<'a, T> = RCow<RSlice<'a, T>, RVec<T>>;
impl<B: IntoOwned> RCow<B, B::Owned> {
fn make_mut(&mut self) -> &mut B::Owned {
match self {
RCow::Borrowed(x) => {
*self = RCow::Owned(x.into_owned());
if let RCow::Owned(x) = self {
x
} else {
unreachable!()
}
},
RCow::Owned(x) => x,
}
}
}
impl<B> PartialEq for RCow<B, B::Owned>
where
B: QueryOwnedType,
B::Target: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl<B> Eq for RCow<B, B::Owned>
where
B: QueryOwnedType,
B::Target: Eq,
{}
impl<B> PartialOrd for RCow<B, B::Owned>
where
B: QueryOwnedType,
B::Target: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(&**self).partial_cmp(&**other)
}
}
impl<B> Ord for RCow<B, B::Owned>
where
B: QueryOwnedType,
B::Target: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
(&**self).cmp(&**other)
}
}
impl<B> Deref for RCow<B, B::Owned>
where
B: QueryOwnedType,
{
type Target = B::Target;
fn deref(&self) -> &Self::Target {
match self {
RCow::Borrowed(x) => x,
RCow::Owned(x) => x.borrow(),
}
}
}
fn eq_rcow<'a, 'b, T>(left: &BCow<'_, T>, right: &BCow<'_, T>) -> bool
where
T: PartialEq
{
RCow::eq(left, right)
}
fn cmp_rcow<'a, 'b, T>(left: &BCow<'a, T>, right: &BCow<'b, T>) -> Ordering
where
T: Ord
{
RCow::cmp(left, right)
}
fn eq_rcow_str<'a, 'b>(left: &RCowStr<'a>, right: &RCowStr<'b>) -> bool {
RCow::eq(left, right)
}
fn cmp_rcow_str<'a, 'b>(left: &RCowStr<'a>, right: &RCowStr<'b>) -> Ordering {
RCow::cmp(left, right)
}
fn eq_rcow_slice<'a, 'b, T>(left: &RCowSlice<'a, T>, right: &RCowSlice<'b, T>) -> bool
where
T: PartialEq
{
RCow::eq(left, right)
}
fn cmp_rcow_slice<'a, 'b, T>(left: &RCowSlice<'a, T>, right: &RCowSlice<'b, T>) -> Ordering
where
T: Ord
{
RCow::cmp(left, right)
}
fn main() {
{
let mut hi = RCowStr::Borrowed("hello".into());
hi.make_mut().push_str(" world");
println!("{hi:?}");
}
{
let value = 0u8;
let left = BCow::Borrowed(&value);
let right = BCow::Borrowed(&value);
dbg!(cmp_rcow(&left, &right));
}
{
let rstr = RStr::from_str("abc");
let left = RCowStr::Borrowed(rstr.clone());
let right = RCowStr::Borrowed(rstr.clone());
dbg!(cmp_rcow_str(&left, &right));
}
} |
Nice! So if
|
Ugh... After the PR that should've fixed this, I've ran into other (worse) errors. One of them is that
In summary, I am going to lose my sanity, honestly. Is it possible to remove the vtable from Playground with proof: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=57e81d749461030ab5f590368253f6f2 Also, here's the test I'm using to prove that the lifetimes work fine, for referenceuse abi_stable::std_types::{RCowStr, RHashMap, RSlice, RStr, RString, RVec, Tuple2};
use std::{borrow::Cow, collections::HashMap, cmp::Ordering};
fn cmp_string<'a, 'b>(left: &String, right: &String) -> Ordering {
left.cmp(right)
}
fn cmp_rstring<'a, 'b>(left: &RString, right: &RString) -> Ordering {
left.cmp(right)
}
fn cmp_string2<'a, 'b>(left: &String, right: &String) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rstring2<'a, 'b>(left: &RString, right: &RString) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_str<'a, 'b>(left: &'a str, right: &'b str) -> Ordering {
left.cmp(right)
}
fn cmp_rstr<'a, 'b>(left: &RStr<'a>, right: &RStr<'b>) -> Ordering {
left.cmp(right)
}
fn cmp_str2<'a, 'b>(left: &'a str, right: &'b str) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rstr2<'a, 'b>(left: &RStr<'a>, right: &RStr<'b>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_vec<'a, 'b>(left: &Vec<&'a str>, right: &Vec<&'b str>) -> Ordering {
left.cmp(right)
}
fn cmp_rvec<'a, 'b>(left: &RVec<&'a str>, right: &RVec<&'b str>) -> Ordering {
left.cmp(right)
}
fn cmp_vec2<'a, 'b>(left: &Vec<&'a str>, right: &Vec<&'b str>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rvec2<'a, 'b>(left: &RVec<&'a str>, right: &RVec<&'b str>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_tpl<'a, 'b>(left: &(&'a str, u8), right: &(&'b str, u8)) -> Ordering {
left.cmp(right)
}
fn cmp_rtpl<'a, 'b>(left: &Tuple2<&'a str, u8>, right: &Tuple2<&'b str, u8>) -> Ordering {
left.cmp(right)
}
fn cmp_tpl2<'a, 'b>(left: &(&'a str, u8), right: &(&'b str, u8)) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rtpl2<'a, 'b>(left: &Tuple2<&'a str, u8>, right: &Tuple2<&'b str, u8>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_slice<'a, 'b>(left: &[&'a str], right: &[&'b str]) -> Ordering {
left.cmp(right)
}
fn cmp_rslice<'a, 'b>(left: &RSlice<&'a str>, right: &RSlice<&'b str>) -> Ordering {
left.cmp(right)
}
fn cmp_slice2<'a, 'b>(left: &[&'a str], right: &[&'b str]) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rslice2<'a, 'b>(left: &RSlice<&'a str>, right: &RSlice<&'b str>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_cowstr<'a, 'b>(left: &Cow<'a, str>, right: &Cow<'b, str>) -> Ordering {
left.cmp(right)
}
fn cmp_rcowstr<'a, 'b>(left: &RCowStr<'a>, right: &RCowStr<'b>) -> Ordering {
left.cmp(right)
}
fn cmp_cowstr2<'a, 'b>(left: &Cow<'a, str>, right: &Cow<'b, str>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_rcowstr2<'a, 'b>(left: &RCowStr<'a>, right: &RCowStr<'b>) -> Option<Ordering> {
left.partial_cmp(right)
}
fn cmp_hashmap<'a, 'b>(left: &HashMap<u8, &'a str>, right: &HashMap<u8, &'b str>) -> bool {
left.eq(right)
}
fn cmp_rhashmap<'a, 'b>(left: &RHashMap<u8, &'a str>, right: &RHashMap<u8, &'b str>) -> bool {
left.eq(right)
} [1] https://doc.rust-lang.org/nomicon/subtyping.html#variance |
I wonder, since we're deep in unsafe terretory, and the vtable is internally managed anyway, would something like this work: sneaky, dirty but perhaps possible? |
From my experimentation, it causes the same kind of lifetime issues to do enum RCow<B: QueryOwnedType>{
Borrowed(B),
Owned(B::Owned),
} as before the redesign. So removing the
Yes. I split it to remove
I could remove it, but then it'll be
Yes, I'll make it work, it'll be bundled along with the other breaking changes to make more lifetimes covariant (I don't have a planned release date for that 0.11.0 release). |
Yup, you're completely right. I've changed it back to
Yeah if we have two generic parameters then it makes sense to have the alias.
Do you mean you want to do it yourself? You can make a PR over #80, any help is very much appreciated :) |
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. |
Fixing the lifetime issues (WRT RCow) in #75 Changed how `RCow` is represented, requiring uses of the `RCow` type to be updated. Added `RCowVal`, `RCowStr`, and `RCowSlice` type aliases to make `RCow` usable. Updated uses of `RCow` in `abi_stable` to make it compile, the repository will be fixed in a latter commit. Updated RCow tests. Added tests for comparison traits, conversion traits. Added `IntoOwned` trait. Made the comparison traits accept RCows with different type arguments. Added these conversion impls: - `From<&'a RVec<T>> for RCowSlice<'a, T>` - `From<&'a Vec<T>> for RCowSlice<'a, T>` Now the conversion impls between `Cow` and `RCow` are non-trivial, using the new `RCowCompatibleRef` trait to simplify some bounds. Now `RCow` only implements `IntoReprRust`/`AsRef`/`Borrow` for `RCowVal`, `RCowSlice`, and `RCowStr`. Removed `BorrowOwned` trait.
This has finally been fixed and can be closed. |
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up * Fix metronome plugin See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up * Fix metronome plugin See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up * Fix metronome plugin See rodrimati1992/abi_stable_crates#75
* Back to attempt with single value * Go back to known_key stub approach * Update to new `RCow` * Lots of fixes and cleaning up * Fix metronome plugin See rodrimati1992/abi_stable_crates#75
Fixing the lifetime issues (WRT RCow) in #75 Changed how `RCow` is represented, requiring uses of the `RCow` type to be updated. Added `RCowVal`, `RCowStr`, and `RCowSlice` type aliases to make `RCow` usable. Updated uses of `RCow` in `abi_stable` to make it compile, the repository will be fixed in a latter commit. Updated RCow tests. Added tests for comparison traits, conversion traits. Added `IntoOwned` trait. Made the comparison traits accept RCows with different type arguments. Added these conversion impls: - `From<&'a RVec<T>> for RCowSlice<'a, T>` - `From<&'a Vec<T>> for RCowSlice<'a, T>` Now the conversion impls between `Cow` and `RCow` are non-trivial, using the new `RCowCompatibleRef` trait to simplify some bounds. Now `RCow` only implements `IntoReprRust`/`AsRef`/`Borrow` for `RCowVal`, `RCowSlice`, and `RCowStr`. Removed `BorrowOwned` trait.
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
fails with the following 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:
The text was updated successfully, but these errors were encountered: