-
Notifications
You must be signed in to change notification settings - Fork 94
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
Implement sound map value aliasing #83
Conversation
Codecov Report
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a suggestion to resolve nested evmaps
I'd like to draw attention to a couple of key unsafeties that I'd like a second opinion on before I merge this (though arguably this is definitely better than what we had, which we know is unsound...): First, aliasing a Next, extracting a Then, to complete the circle, the code that causes The actual drop code I think is uncontroversial:
|
As with I don't believe |
Yes, I think I agree that |
So while I still think the explicit drop option has merit, it's definitely not the only one. First off, there may be something I'm missing here, and please correct me if I'm wrong - but considering If that's not an option, I think it would the flag would need to be map-local rather than thread-local. We could have some kind of |
@MayaWolf Yes, unfortunately that is unsound. That's in part why the current implementation on Map-local would be nice, but sadly that's also hard unless we're willing for every value to hold a pointer back to a bool shared with the map that it uses to check whether things should be dropped. That's potentially a lot of memory overhead. And ignoring that, this might also be tricky to do safely, since we'd be trying to get at a value that is inside the map which we hold out a I have an idea that might let us do this without overhead and without the thread-local though. It hinges on this being safe: rust-lang/unsafe-code-guidelines#35 (comment). Commit coming soon. |
Unfortunately it doesn't seem like such a transmute is safe either 😢 |
I'll be honest, even after having read through this multiple times I'm confused as to what the actual issue of casting between I think an important thing to consider is we do control the That's obviously not a perfect solution at all, but I don't think it makes sense to sacrifice efficiency or versatility in trying to work around something that honestly seems like it's "theoretically questionable, but practically fine." Unless I'm just underestimating how big of a problem this really is, in which case the |
Yeah, it's fairly involved. The best relevant summary I know of is from the nomicon's page on
That's what bites us here. Even though trait Wonky { type Weird; }
struct HashMap<K, V: Wonky>(K, V::Weird, V);
impl<T> Wonky for Aliased<T> { type Weird = u32; }
impl<T> Wonky for T { type Weird = u16; } That is obviously very contrived, but it does demonstrate a case where the layout of the wrapper changes if you cast While it's tempting to say "We know that The factor idea I am also super hesitant about unfortunately — storing an extra (identical) pointer for every value is some pretty huge overhead, and not one I'm very keen to swallow. I would rather say "don't embed one |
You can see my current tinkering down that path ignoring the possible unsoundness over on sound-aliasing...drop-by-generic-type (the commit message has more details). |
For comparison, here is my attempt at making dropping Aliased's values explicit. I frankly don't know how this affects performance, that would need testing - but in terms of complexity it's entirely manageable. I had to make |
It seems I can't leave comments on the commit you posted (or maybe I'm just not pressing the right buttons) - but what's the advantage to passing around
|
@MayaWolf It's funny, because that's kind of what evmap used to do. "Back in the day" evmap would explicitly forget values in My main concern with the explicit drop approach is that is requires somewhat careful manual and error-prone work to ensure that everything is dropped correctly, and it's not always the case that the implementations even have the requisite methods to do so safely. For example, I'm not even sure it's okay to drop through a I want to stress that I'm not trying to say that explicit drop isn't the way to go. It's probably still preferable to the thread-local magic. I'm more saying that I want to try to push a bit more on my current strategy to see if I can get it "for free".
Ah, yes, it does. Specifically, there has to be no way for any code outside of the current crate to distinguish between the types we are casting between in any way. If we made it be
You are not the only one. I keep messing up "absorb" and the old "apply" myself :p |
Does it? It might be public from the point of view of |
If it's exposed by Unfortunately, even if you did that, transmuting from |
Oh, I get it now! So basically, the problem we're trying to work around is that someone might do an I'll check out your branch tomorrow and see whether I can come up with a solution to the problem you mentioned! |
Exactly! In theory I think the problem is minor, I just haven't been able to wrap my head around how to fix it. We could fix the particular issue with |
I think what we're going to have to do is define a EDIT: I'll add that all the methods that are currently on Now, that doesn't take care of the |
Yeah, I've had another look through it and thought about it, and I agree that this probably needs a second wrapper around Values.I can't really come up with another way that wouldn't end up exposing |
Ah, so, it's not actually a problem to expose the |
I'm getting real close. Unfortunately, I still have no idea how to go about hiding it there except by making it unable to go through |
Posted on users.r-l.o: https://users.rust-lang.org/t/private-type-in-bound-of-public-method/52375 |
Huh, well, that was as simple as having |
1de0cef
to
f53e0ee
Compare
This implementation gets rid of the unsound `ShallowCopy` trait (#74 & rust-lang/unsafe-code-guidelines#35 (comment)), and replaces it with a wrapper type around aliased values. The core mechanism is that the wrapper type holds a `MaybeUninit<T>`, and aliases it by doing a `ptr::read` of the whole `MaybeUninit<T>` to alias. It then takes care to only give out `&T`s, which is allowed to alias. To drop, the implementation casts between two different generic arguments of the new `Aliased` type, where one type causes the `Aliased<T>` to drop the inner `T` and the other does not. The documentation goes into more detail. The resulting cast _should_ be safe (unlike the old `ManuallyDrop` cast). The removal of `ShallowCopy` makes some of the API nicer by removing trait bounds, and obviates the need for `evmap-derive`. While I was going through, I also took the liberty of tidying up the external API of `evmap` a bit. The implementation passes all tests, and I _think_ it is sound (if you think it's not, please let me know!). Note that this does _not_ take care of #78. Fixes #74. Fixes #55 since `ShallowCopy` is no longer neeeded. Also fixes #72 for the same reason.
f53e0ee
to
416ccef
Compare
I've released @MayaWolf If you run into issues with it for your implementations, please let me know! |
This implementation gets rid of the unsound
ShallowCopy
trait (#74 &rust-lang/unsafe-code-guidelines#35 (comment)),
and replaces it with a wrapper type around aliased values.
The core mechanism is that the wrapper type holds a
MaybeUninit<T>
,and aliases it by doing a
ptr::read
of the wholeMaybeUninit<T>
toalias. It then takes care to only give out
&T
s, which is allowed toalias. To drop, the implementation sets a thread-local that the wrapper
uses to determine if it should truly drop the inner
T
(i.e., toindicate that this is the last copy, and the
T
is no longer aliased).The removal of
ShallowCopy
makes some of the API nicer, and obviatesthe need for
evmap-derive
. Unfortunately the introduction of thewrapper also complicates certain bounds which now need to know about the
wrapping. Overall though, an ergonomic win.
The implementation passes all tests, and I think it is sound (if you
think it's not, please let me know!). The one bit that I'm not sure
about is the thread-local if a user nests
evmap
s (since they'll sharethat thread local).
Note that this does not take care of #78.
Fixes #74.
Fixes #55 since
ShallowCopy
is no longer neeeded.This change is