-
Notifications
You must be signed in to change notification settings - Fork 4
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
Introduce some macros for soundness #11
Conversation
For a few Rust versions now, sealed traits no longer need to (ab)use public-in-private tricks, since private supertraits are allowed. With a unsafe fn new_scope(...) -> impl Scope<...>, the type Wrapper no longer needs to be public either.
…sed-in expressions. Especially unsafe is important, as the macro would otherwise be unsound, allowing unsafe code in e.g. a scop!() without explicit usage of `unsafe` from the user.
…tion I believe rustfmt support is important enough that we should encourage this encourage this style, and even force the inner braces to help with that. Also, this makes the macro just take a simple $b:block, which makes the rendered docs slightly more clear what is expected syntactically, compared to the status quo of {$($t:tt)*}.
I've collected some suggestions / improvements from my first review of this PR into a PR to your PR branch ;-) For more information look at the commit messages. |
…ases involving lifetimes. For example if we have something like ```rs fn try_zip_files<'scope, 'a: 'scope, R: Read + Seek + 'scope>( mut archive: ZipArchive<R>, s: &'a str, ) -> impl nolife::Scope<Family = ZipReaderFamily, Output = ZipResult<nolife::Never>> + 'scope { scope!({ /*use s */ s; // ... } } ``` this code fails before this commit. (The general pattern is to add a `+ 'scope` lifetime and then add `…: 'scope` bounds for all captured lifetimes and type parameters.) This still leaves adding an actual test case for such a use case (and documentation for it) as TODO.
That’s a good concern. I’ll look into that, too. |
Theoretically, since the In practice, Rust is making this super difficult, as anything containing a DST is super difficult to handle. I tried to mark the associated I thought that maybe we could erase a |
I’ve been thinking (not too deeply yet) of about creating an approach of struct GeneralScope<S: Scope<Output = Never>> (GeneralScopeInner<S>);
enum GeneralScopeInner<S: Scope<Output = Never>> {
Initial(S),
Running(MaybeUninit<S::Future>, State<S::Family>, PhantomPinned),
Poison,
} and giving the pub fn enter<'borrow, Output, G>(self: Pin<&'borrow mut Self>, f: G) -> Output
where
G: for<'a> FnOnce(&'borrow mut <S::Family as Family<'a>>::Family) -> Output, which would already help decouple the boxing from the logic. So instead of Now, I think that one could then place the essential parts of Pin<Box<GeneralScope<S>>> into Pin<Box<dyn GeneralScopeTrait<Family = S::Family>>> via coercion. Footnotes
|
Hey, thanks for the suggestions, it is true that a trait like However I remember specifically avoiding a direct More generally I tried to be very cautious to the paths that allow accessing the self-referential part (the one in I'll try and see if I can come up with another solution that doesn't involve modifying the |
I merged your commits from #12. Thank you they were very useful ❤️ |
Sure, that makes a lot of sense; leaving a more generalized approach probably to primitives like this to arrive some time in the future. |
I pushed an update that adds an
I updated my gist to offer an alternative |
I'm seeing even the currently published approach of Regarding Also, I do think I have a relatively complete version in my head, of the alternative approach of wrapping the I should try to write that down tomorrow, for comparison to see which approach seems "nicer" overall. |
Made an experiment of trying to actually write it down and it looks like I missed something that it makes it so it doesn't work after all: The problem comes from tagging So it's probably best to rather work with the approach of this PR for not. Of course, my other two sections of my previous comment are unrelated to this experiment, and thus still relevant as potential ways to improve the code in this PR (or in future PRs). |
Thank you for your attempt, it is good to know what works and what doesn't.
I'll try an approach reliant on manual type erasure rather than dyn trait, similar to anyhow's approach. So that you can build a If that works, I'm wondering if I should make that approach the only option, because most of the time if you're using a BoxScope it'll be to store it in a struct and very frequently you don't want that struct to be generic because of that 🤔
I think you're right I'll try to revert that part of the changes |
fixed the tests and now I have an actual miri failure related to nofuture. I'll take a look later. |
Ah might be the |
Testing non-compiling examples in miri is not very interesting, and there is a bug where a test does not compile under normal nightly but **does** compile under miri
- Drop erased future with the original layout of the future - Never construct a reference to the erased future - Access the erased future on a chain where no reference is ever created. This is important for stacked borrows that check the size of the allocation. Creating any reference to a StackScope<T, NoFuture<Erased>> creates an access only for the size of that object, eschewing the actual future size.
Fixed the NoFuture part, there were multiple soundness issue.
That certainly was a journey 😅 but now miri is happy in both stacked borrow and tree borrows @steffahn, do you think there will remain any soundness issues once I merge this and #9? |
I still want to try usage of ordinary Regarding other soundness issues, that's hard to know. The more subtle soundness issues I find in crates, the less confidently I'd ever say there none left. There are none I'm currently aware of once these issues are fixed, but a finalized version of the code that fixes it is the very basis of even being able to start really reviewing the whole thing more in-depth. |
Sure that would be great if we could make it work. I don't see how right now, due to the tension between:
Now, there might be another approach I'm not seeing here, and the limitation of (2) is not fundamental, it just appears to be a limitation of the Rust language at the time, which is why we can fake it by reimplementing an erased future ourselves. I agree, as much as I loved writing the code in To me the best resolution would be for some kind of named opaque types to land in Rust, so the |
Sure, I'll re-read the whole thing to check the |
With repr(C) you can turn a MaybeUninit-containing struct into one that contains the value directly in the field, behind the pointer indirection (pointer cast). I'm currently on a hike, so I can't write this down too much in detail - as mentioned I'll gladly try an implementation myself, perhaps later today, if I find the time for it. |
Sure, enjoy your hike and take your time, I can wait 😊 Went for a two hours walk myself (does it still qualifies as a "walk" after two hours?) Hope your afternoon is sunny ☀️ |
Looks like this works just fine. Here's the code: https://github.com/steffahn/nolife/commits/use_trait_objects/ I've worked not from the latest commit, but from 22448c8 so you can't merge this but would need to Also, I haven't worked through documenting all the |
Actually, I've just added a revert commit myself now, the code is in #13. |
oh wow, so your solution was to have the That's very smart, I can guarantee that before today I would never have thought of that one. TIL. I like the I feel like my mind isn't sharp enough tonight to properly review this (pretty much the entirety of Thank you again for your contribution, you are making this library better 👍 |
(btw, I updated my gist to use your branch, and it looks nice 👍) |
Also, I think my changes might have eliminated a case of memory leak if the |
v0.3.x used a separate It is true that we are missing a I added a test to my branch. I checked that it fails miri with a leak on |
Ah, of course – the things that aren’t possible with |
Fixes #8