Skip to content

Commit 0b25bf4

Browse files
committed
Include signatures for ThreadLocalKey and change for to unalias
1 parent a578f50 commit 0b25bf4

File tree

2 files changed

+22
-4
lines changed

2 files changed

+22
-4
lines changed

project-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ substracting
3333
subtyping
3434
thrd
3535
tigerbeetle
36+
unalias
3637
unaliased
3738
uncopyable
3839
unergonomic

src/natural-aliasing.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Natural aliasing model
22

3-
*Draft 2025-07-07*
3+
*Draft 2025-07-10*
44

55
<!--toc:start-->
66
- [Natural aliasing model](#natural-aliasing-model)
@@ -125,8 +125,8 @@ It would be safe to send a type across the thread boundary only if it's aliased
125125
To avoid to talk about plain borrows, consider `Rc<'a, T>` implemented using new `Cell<'a, usize>` as a reference counter.
126126
It is safe to send `a: Rc<'a, T>` to another thread if there isn't any other `b: Rc<'a, T>` left on the old thread.
127127
But more than that, if there is another `b: Rc<'a, T>`, we still could send both of them `(a, b)` across threads.
128-
I have found type annotation for [higher-ranked lifetimes] `(a, b): for<'a> (Rc<'a, T>, Rc<'a, T>)`, although formally ambiguous, to be quite fitting.
129-
Now you can see yourself why `&mut T` would be just a non-copyable version of `for<'a> &Cell<'a, T>`.
128+
I have found type annotation similar to [higher-ranked lifetimes] `(a, b): unalias<'a> (Rc<'a, T>, Rc<'a, T>)`, to be quite fitting.
129+
Now you can see yourself why `&mut T` would be just a `unalias<'a> &Cell<'a, T>`, which means this type has to be uncopyable before instantiating `'a` with a free lifetime.
130130

131131
From this we could even restore the original `Send` oriented design.
132132
The `!Send` implementation on a type essentially tells that utilized memory region could be (non-atomically, without synchronization) aliased from the *current thread*.
@@ -138,10 +138,27 @@ The solution to that problem would be to abstract assumption into a type, let's
138138
But you shouldn't be able to prove that `'a` aliasing lifetime does not occur somewhere else, so you won't ever be able to send it across threads.
139139
Any function requiring thread-unsafe access to thread-locals would have to get this type through its arguments.
140140
This then would be reflected in the function signature, which would inform whether function body is sendable across threads or not.
141+
Simplifying, this is how it would look:
142+
143+
```rust
144+
fn main(key: ThreadLocalKey<'_>) {}
145+
146+
fn thread_spawn<F, R>(f: F) -> R
147+
where
148+
F: for<'a> FnOnce(ThreadLocalKey<'a>) -> R
149+
{}
150+
151+
impl LocalKey<T> {
152+
fn with<F, R>(&'static self, key: &ThreadLocalKey<'_>, f: F) -> R
153+
where
154+
F: FnOnce(&T) -> R,
155+
{}
156+
}
157+
```
141158

142159
This way you could imagine a `Future` gets `ThreadLocalKey<'a>` through its `poll` method,
143160
which explains why storing any thread-unsafe type `T: 'a` should make the compiler assume future is thread-unsafe as a whole.
144-
Unless that future's internal structure contains types only with `for<'a>` bounded aliasing lifetimes!
161+
Unless that future's internal structure contains types only with `unalias<'a>` bounded aliasing lifetimes!
145162

146163
**You should notice that now the thread-safe property of a type could be defined solely from the *type's boundary*, i.e. its safe public interface.**
147164
I will name this rule the *fundamental aliasing rule*, although pretentious, in the context of our theory it is worth its name.

0 commit comments

Comments
 (0)