When instantiating global variables with thread_local! macro values end up wrapped in LocalKey struct which gives access to a data through with function.
However, if I understand correctly, if the trait doesn't implement Sync, then taking the value out of with is safe, or is it not?
Here is an example:
Found this answer based on which !Send also needs to be specified, so that you couldn't share the data with another thread as well.
It is unsound to take a &'static reference to a thread-local value, even if it is !Sync, because the value is dropped when the thread terminates, and while the destructor of a different thread-local is running, the reference could be used after its referent has been freed.
It's not okay if the thread local API gives out static references to the contents, because they could be moved to another thread and then used after the thread exits. If that happens you have a use after free.
And using a scope like how with works is the only way for it to not hand out a static reference. The scope defines the lifetime of the reference, and we know that the lifetime is not too long as the thread can't exit during the scope.
Sorry, I didn't understand this example. The reference can only be used in a single thread, implied by !Sync, how can another thread get a reference to this value then?
the other problems like access after destruction still apply
Can you please provide an example on this (or maybe describe how it can be done, if the example will be involved)? Because in my context Wrapper contains a pointer to which you cannot get access, and all functions on the Wrapper return owned values. So it is non-Sync all the way down.
I’m not sure I understand what you mean here, in the context of OP’s question. They were talking about a !Sync type. Perhaps you can give a code example?
If T: !Sync, then you cannot put a &'static T into a global variable. And if your mention of thread_local was supposed to show some type of “global” that supports this, anyways, then your last step of “observe the newly-set, now-dangling reference in the main thread” won’t work, because you cannot observe the value set to a thread_local on one thread in another thread.
Edit: Bit of a race condition… you did add a code example now. However it features a thread_local of type String which is not a !Sync type (mind the exclamation mark).
Of course @quinedot in the meantime already posted the relevant example that does show unsoundness after all, but it is somewhat different.
But how does it help the API of LocalKey? Unless I'm mistaken, it's not possible to restrict the set of types that work with LocalKey to !Sync types only. (And at the very least, the current API does not do that at the moment.)
Therefore, the API must anticipate being used with a type to which references cross thread boundaries.
No, it wouldn’t help the API at all indeed. You can’t ask for !Sync in a function signature, and a !Sync type can contain Sync types, anyways.
I think OP’s quest for understanding the nuances might have gone beyond just the possibilities of alternative API design, but only in terms of API design, you’re right, the !Sync argument doesn’t help much at all.
Even without a general API, one could still use unsafe for a concrete known-!Sync type[1] and wonder if that’s sound; which is kind-of what OP did here.