1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Static `Send`/`Sync` contract tests for the smart pointers.
//!
//! These tests do not run any code at runtime — they assert the
//! desired auto-trait behaviour at compile time. They will fail to
//! compile if a future refactor either narrows the auto-trait set
//! (e.g. by introducing a `!Send` field) or widens it past the
//! intended bounds.
use multitude::{Alloc, Arc, Arena, Box, Rc};
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
// Compile-time `!Send` / `!Sync` assertions via the two-blanket-impl
// ambiguity trick: `probe` resolves unambiguously only when the type does
// *not* implement the trait. If it does, both blanket impls apply and the
// call fails to compile — exactly the regression we want to catch.
trait AmbiguousIfSend<A> {
fn probe() {}
}
impl<T: ?Sized> AmbiguousIfSend<()> for T {}
impl<T: ?Sized + Send> AmbiguousIfSend<u8> for T {}
fn assert_not_send<T: ?Sized>() {
let _ = <T as AmbiguousIfSend<_>>::probe;
}
trait AmbiguousIfSync<A> {
fn probe() {}
}
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
impl<T: ?Sized + Sync> AmbiguousIfSync<u8> for T {}
fn assert_not_sync<T: ?Sized>() {
let _ = <T as AmbiguousIfSync<_>>::probe;
}
#[test]
fn arena_is_send() {
// `Arena: Send` is intended to be auto-derived from its fields.
// Lock that contract here so a future field addition that
// accidentally introduces a `!Send` type fails to compile.
// `Arena` is intentionally `!Sync` (interior `RefCell` /
// `UnsafeCell`), so only `Send` is asserted.
assert_send::<Arena>();
// And a runtime cross-thread move:
let arena: Arena = Arena::new();
let _ = arena.alloc(7_u64);
std::thread::scope(|s| {
let h = s.spawn(move || {
let x = arena.alloc(99_u64);
*x
});
assert_eq!(h.join().unwrap(), 99);
});
}
#[test]
fn arc_is_send_sync_when_t_is() {
assert_send::<Arc<u64>>();
assert_sync::<Arc<u64>>();
assert_send::<Arc<[u8]>>();
assert_sync::<Arc<[u8]>>();
let arena: Arena = Arena::new();
let a = arena.alloc_arc(42_u64);
std::thread::scope(|s| {
let h = s.spawn(move || *a);
assert_eq!(h.join().unwrap(), 42);
});
}
#[test]
fn box_is_send_sync_when_t_is() {
// `Box<T, A>` mirrors `std::Box`: `Send` when `T: Send` and
// `A: Send`; `Sync` when `T: Sync` and `A: Sync`. The backing
// storage uses an atomic-refcounted chunk, so dropping the
// `Box` on a different thread is sound.
assert_send::<Box<u64>>();
assert_sync::<Box<u64>>();
assert_send::<Box<[u8]>>();
assert_sync::<Box<[u8]>>();
let arena: Arena = Arena::new();
let b = arena.alloc_box(42_u64);
std::thread::scope(|s| {
let h = s.spawn(move || *b);
assert_eq!(h.join().unwrap(), 42);
});
}
#[test]
fn box_str_is_send_sync() {
// `Box<str, A>` is `Send` when `A: Send` and `Sync` when `A: Sync`
// (`str` is itself `Send + Sync`).
assert_send::<Box<str>>();
assert_sync::<Box<str>>();
let arena: Arena = Arena::new();
let b = arena.alloc_str_box("hello");
std::thread::scope(|s| {
let h = s.spawn(move || b.len());
assert_eq!(h.join().unwrap(), 5);
});
}
#[test]
fn arc_str_is_send_sync() {
assert_send::<Arc<str>>();
assert_sync::<Arc<str>>();
}
#[test]
fn alloc_send_sync_follow_t() {
use core::cell::Cell;
// `Alloc<'a, T>` wraps `&'a mut T`, so it inherits the auto traits of
// `&mut T`: `Send` when `T: Send` (`impl<T: Send + ?Sized> Send for &mut T`)
// and `Sync` when `T: Sync` (`impl<T: Sync + ?Sized> Sync for &mut T` — a
// shared `&&mut T` only permits *reading* through it, so sharing is sound
// when `T: Sync`). These assertions lock that contract in.
assert_send::<Alloc<'static, u64>>();
assert_sync::<Alloc<'static, u64>>();
assert_send::<Alloc<'static, [u8]>>();
assert_sync::<Alloc<'static, [u8]>>();
// `Cell<T>` is `Send` but `!Sync`, so `Alloc<Cell<_>>` must follow suit —
// proving `Sync` genuinely tracks `T: Sync` rather than being unconditional.
assert_send::<Alloc<'static, Cell<u64>>>();
assert_not_sync::<Alloc<'static, Cell<u64>>>();
}
#[test]
fn rc_is_not_send_or_sync() {
// `Rc<T, A>` uses a *non-atomic*, unaligned per-handle strong count
// (read/written with `read_unaligned`/`write_unaligned`), so it must never
// cross threads. It carries no `unsafe impl Send`/`Sync`, and its
// `NonNull<u8>` + `PhantomData<(*const T, A)>` fields keep it `!Send` and
// `!Sync` automatically for every `T`/`A`. This test fails to compile if a
// future refactor accidentally makes `Rc` `Send` or `Sync` — which would
// be unsound, since two threads could then race the non-atomic count.
assert_not_send::<Rc<u64>>();
assert_not_sync::<Rc<u64>>();
assert_not_send::<Rc<[u8]>>();
assert_not_sync::<Rc<[u8]>>();
assert_not_send::<Rc<str>>();
assert_not_sync::<Rc<str>>();
}
#[test]
fn drain_and_splice_are_not_send_or_sync() {
// `Vec` is thread-affine (`!Send`/`!Sync`): its `drain`/`splice`
// iterators borrow it mutably and restore the surviving tail in `Drop`.
// They must inherit that `!Send`/`!Sync` — otherwise a live `Drain`
// could migrate to another thread and run the in-place tail restore
// from a foreign thread, which is unsound. The `PhantomData<&'d mut
// Vec<..>>` marker locks this in; this test fails to compile if a future
// refactor accidentally makes either iterator `Send`/`Sync`.
use allocator_api2::alloc::Global;
use multitude::vec::{Drain, Splice};
assert_not_send::<Drain<'static, 'static, u64, Global>>();
assert_not_sync::<Drain<'static, 'static, u64, Global>>();
assert_not_send::<Splice<'static, 'static, core::iter::Empty<u64>, Global>>();
assert_not_sync::<Splice<'static, 'static, core::iter::Empty<u64>, Global>>();
}