-
Notifications
You must be signed in to change notification settings - Fork 34
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
Why does with_* require HRTB for non-covariant / is there a way to make this lifetime work #38
Comments
I don't think that's the cause of the issue here -- this example doesn't actually use |
It looks like this is a limitation of how the library checks for soundness. Higher ranked lifetimes are used as wildcards to say that the 'this lifetime has no faithful representation in the language and so the code must work for every possible lifetime to be safe. I don't see a way to propagate the fact that the two |
When you say "all the data in a single struct" what do you mean? (Thanks for taking the time to look at this!) |
If you put all the fields in a single struct, then when you use |
The current design is supposed to allow you to create multiple |
I think I've been able to turn this into a feature request, which is hopefully sound (😬) though I'm not sure what the right API is: #[ouroboros::self_referencing]
struct O1 {
data: Arc<X>,
#[borrows(data)]
#[covariant]
value: T1<'this>,
}
#[ouroboros::self_referencing]
struct O2 {
data: Arc<X>,
#[borrows(data)]
#[covariant]
value: T2<'this>,
}
fn op(o: &O1, for<'this> impl FnOnce(&'this X, &T1<'this>) -> T2<'this>) -> O2 {
// TODO
} This is somewhat specific to cases where an Because of the way lifetimes work + the fact that Does this make sense to you? Do you see any soudness issues? If you have an idea for what the API should be, I'd be happy to contribute a PR for this! |
I think as written the function could be used unsoundly: struct T1<'a>(&'a ());
struct T2<'a>(&'a ());
let o1 = make_o1_instance();
let o2 = op(&o1, |x, t1| T2(t1.0));
drop(o1);
// UB:
// o2.with_value(); |
I attempted to actually implement that example: use std::sync::Arc;
#[ouroboros::self_referencing]
struct O1 {
data: Arc<X>,
#[borrows(data)]
#[covariant]
value: T1<'this>,
}
#[ouroboros::self_referencing]
struct O2 {
data: Arc<X>,
#[borrows(data)]
#[covariant]
value: T2<'this>,
}
struct X(());
struct T1<'a>(&'a ());
#[derive(Debug)]
struct T2<'a>(&'a ());
unsafe fn relifetime<'a, 'b>(t: &'a T1<'a>) -> &'b T1<'b> {
std::mem::transmute(t)
}
fn op(o: &O1, f: impl for<'this> FnOnce(&'this X, &T1<'this>) -> T2<'this>) -> O2 {
O2::new(Arc::clone(&o.borrow_data()), |x| {
f(&x, unsafe { relifetime(o.borrow_value()) })
})
}
fn main() {
let o1 = O1::new(Arc::new(X(())), |x| T1(&x.0));
let o2 = op(&o1, |x, t1| T2(t1.0));
drop(o1);
o2.with_value(|v| println!("{:?}", v));
} And it looks ok under ASAN:
Did I implement what you expected, or did I get some detail wrong? |
Also seems good under miri:
|
It might be showing fine because it's a reference to a zero-sized type, my bad. Try replacing |
Still green under miri. IMO it makes sense that this is fine: both |
Ah, I didn't notice that part. When creating O2, there is nothing guaranteeing that the Arc will be a clone of the old one. If you create a new Arc and have the second field reference the old Arc, then dropping the instance of O1 will cause UB. |
In the general case I agree, but I don't think that situation can ever occur with the |
I think I understand now, I had been looking at it differently. I agree that it's sound, although since it is targeting only one very specific format I would want to hold off on putting it in the library unless there's a more general solution. I'll give it some more thought over the coming days. For now, if you are reliant on doing something specific like this in your code I would say that would justify rolling your own unsafe self-referencing struct. |
Having a macro to generate a proper implementation for this one specific pattern would be equivalent to writing a proper implementation by hand and using that in your code (a la owning_ref) |
Ok! Thanks for considering this. I'll just integrate this into my existing project for now -- will share a link to the PR here once it's done. I think this is a very powerful and useful pattern, so hopefully we can think of a clever way to incorporate it into the design of ouroboros! |
pyca/cryptography#6276 now contains the open-coded implementations of the API discussed here (as well as their usage) |
And https://github.com/pyca/cryptography/blob/ee5c05cfe745c125baa06be1de43c181532cfa58/src/rust/src/ocsp.rs#L595-L614 contains another implementation of this API |
When a tail field contains an
UnsafeCell
it must be#[non_covariant]
which means you cannot have the niceborrow_*
method, alas.I'm struggling to figure out if there's any way to get this minimized code to work (requires nightly, sorry):
The idea is to be able to take an
Arc<OwnedRawCertificateRevocationList>
and project it to aOwnedRawRevokedCertificate
which shares the same underlying data.However, because
with_*
use HRTB and the callback is required to be valid for all lifetimes, the lifetime bound for theRawRevokedCertificate
inside ofc
is bound more tightly thanv
, so this code doesn't work.I am struggling to understand if there's a way to make this work under the current APIs, or failing that if this is because any expanded API would be unsound or if it's just a missing API.
The text was updated successfully, but these errors were encountered: