@shd101wyy/yo 0.1.30 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shd101wyy/yo",
3
3
  "displayName": "Yo",
4
- "version": "0.1.30",
4
+ "version": "0.1.31",
5
5
  "main": "./out/cjs/index.cjs",
6
6
  "module": "./out/esm/index.mjs",
7
7
  "types": "./out/types/src/index.d.ts",
package/std/build.yo CHANGED
@@ -47,7 +47,9 @@ Sanitize :: enum(
47
47
  /// AddressSanitizer — detects memory errors and leaks.
48
48
  Address,
49
49
  /// LeakSanitizer — detects memory leaks only.
50
- Leak
50
+ Leak,
51
+ /// ThreadSanitizer — detects data races in multi-threaded code.
52
+ Thread
51
53
  );
52
54
  export(Sanitize);
53
55
  // ── Step kinds ───────────────────────────────────────────────────────
@@ -281,7 +283,8 @@ executable :: (fn(comptime(config) : Executable) -> comptime(Step))({
281
283
  config.sanitize,
282
284
  .None => "none",
283
285
  .Address => "address",
284
- .Leak => "leak"
286
+ .Leak => "leak",
287
+ .Thread => "thread"
285
288
  );
286
289
  __yo_build_executable(config.name, config.root, config.target, opt_str, alloc_str, san_str);
287
290
  Step(name : config.name, kind : StepKind.Executable)
package/std/imm/list.yo CHANGED
@@ -28,7 +28,7 @@ ListNode :: (fn(comptime(T) : Type, where(T <: Send)) -> comptime(Type))(
28
28
  )
29
29
  )
30
30
  );
31
- // Manual Acyclic impl — ListNode is self-referential, so auto-derivation skips it.
31
+ // SAFETY: Manual Acyclic impl — ListNode is self-referential, so auto-derivation skips it.
32
32
  // This is safe because ListNode is immutable: all operations create new nodes and never
33
33
  // mutate existing ones, making it impossible to form cycles at runtime.
34
34
  impl(forall(T : Type), where(T <: Send), ListNode(T), Acyclic());
@@ -40,7 +40,7 @@ RBNode :: (
40
40
  )
41
41
  )
42
42
  );
43
- // Manual Acyclic impl — RBNode is self-referential, so auto-derivation skips it.
43
+ // SAFETY: Manual Acyclic impl — RBNode is self-referential, so auto-derivation skips it.
44
44
  // This is safe because RBNode is immutable: all tree operations create new nodes
45
45
  // and never mutate existing ones, making it impossible to form cycles at runtime.
46
46
  impl(forall(K : Type, V : Type), where(K <: (Eq(K), Ord(K), Send), V <: Send), RBNode(K, V), Acyclic());
@@ -138,6 +138,209 @@ c_include(
138
138
  ATOMIC_LLONG_LOCK_FREE : i32,
139
139
  ATOMIC_POINTER_LOCK_FREE : i32
140
140
  );
141
+ // SAFETY: C11 atomic typedefs (atomic_bool, atomic_int, etc.) are opaque C
142
+ // types backed by _Atomic-qualified primitives. They contain no heap
143
+ // allocations or thread-local state — they are trivially safe to send
144
+ // across threads. They are Acyclic because they don't participate in Yo's
145
+ // ARC reference-counting cycle analysis.
146
+ impl(atomic_bool, Send());
147
+ impl(atomic_bool, Acyclic());
148
+ impl(atomic_char, Send());
149
+ impl(atomic_char, Acyclic());
150
+ impl(atomic_schar, Send());
151
+ impl(atomic_schar, Acyclic());
152
+ impl(atomic_uchar, Send());
153
+ impl(atomic_uchar, Acyclic());
154
+ impl(atomic_short, Send());
155
+ impl(atomic_short, Acyclic());
156
+ impl(atomic_ushort, Send());
157
+ impl(atomic_ushort, Acyclic());
158
+ impl(atomic_int, Send());
159
+ impl(atomic_int, Acyclic());
160
+ impl(atomic_uint, Send());
161
+ impl(atomic_uint, Acyclic());
162
+ impl(atomic_long, Send());
163
+ impl(atomic_long, Acyclic());
164
+ impl(atomic_ulong, Send());
165
+ impl(atomic_ulong, Acyclic());
166
+ impl(atomic_llong, Send());
167
+ impl(atomic_llong, Acyclic());
168
+ impl(atomic_ullong, Send());
169
+ impl(atomic_ullong, Acyclic());
170
+ impl(atomic_char16_t, Send());
171
+ impl(atomic_char16_t, Acyclic());
172
+ impl(atomic_char32_t, Send());
173
+ impl(atomic_char32_t, Acyclic());
174
+ impl(atomic_wchar_t, Send());
175
+ impl(atomic_wchar_t, Acyclic());
176
+ impl(atomic_int_least8_t, Send());
177
+ impl(atomic_int_least8_t, Acyclic());
178
+ impl(atomic_uint_least8_t, Send());
179
+ impl(atomic_uint_least8_t, Acyclic());
180
+ impl(atomic_int_least16_t, Send());
181
+ impl(atomic_int_least16_t, Acyclic());
182
+ impl(atomic_uint_least16_t, Send());
183
+ impl(atomic_uint_least16_t, Acyclic());
184
+ impl(atomic_int_least32_t, Send());
185
+ impl(atomic_int_least32_t, Acyclic());
186
+ impl(atomic_uint_least32_t, Send());
187
+ impl(atomic_uint_least32_t, Acyclic());
188
+ impl(atomic_int_least64_t, Send());
189
+ impl(atomic_int_least64_t, Acyclic());
190
+ impl(atomic_uint_least64_t, Send());
191
+ impl(atomic_uint_least64_t, Acyclic());
192
+ impl(atomic_int_fast8_t, Send());
193
+ impl(atomic_int_fast8_t, Acyclic());
194
+ impl(atomic_uint_fast8_t, Send());
195
+ impl(atomic_uint_fast8_t, Acyclic());
196
+ impl(atomic_int_fast16_t, Send());
197
+ impl(atomic_int_fast16_t, Acyclic());
198
+ impl(atomic_uint_fast16_t, Send());
199
+ impl(atomic_uint_fast16_t, Acyclic());
200
+ impl(atomic_int_fast32_t, Send());
201
+ impl(atomic_int_fast32_t, Acyclic());
202
+ impl(atomic_uint_fast32_t, Send());
203
+ impl(atomic_uint_fast32_t, Acyclic());
204
+ impl(atomic_int_fast64_t, Send());
205
+ impl(atomic_int_fast64_t, Acyclic());
206
+ impl(atomic_uint_fast64_t, Send());
207
+ impl(atomic_uint_fast64_t, Acyclic());
208
+ impl(atomic_intptr_t, Send());
209
+ impl(atomic_intptr_t, Acyclic());
210
+ impl(atomic_uintptr_t, Send());
211
+ impl(atomic_uintptr_t, Acyclic());
212
+ impl(atomic_size_t, Send());
213
+ impl(atomic_size_t, Acyclic());
214
+ impl(atomic_ptrdiff_t, Send());
215
+ impl(atomic_ptrdiff_t, Acyclic());
216
+ impl(atomic_intmax_t, Send());
217
+ impl(atomic_intmax_t, Acyclic());
218
+ impl(atomic_uintmax_t, Send());
219
+ impl(atomic_uintmax_t, Acyclic());
220
+ // Phase C (THREAD_SAFETY): int-specific atomic operations. The c_include
221
+ // block only declares load/store/exchange for atomic_bool. These Yo-runtime
222
+ // wrappers forward to the C11 _Generic macros in <stdatomic.h> so AtomicI32
223
+ // can use the full atomic API.
224
+ extern(
225
+ "Yo",
226
+ __yo_atomic_load_int :
227
+ fn(obj : *(atomic_int), order : memory_order) -> i32,
228
+ __yo_atomic_store_int :
229
+ fn(obj : *(atomic_int), desired : i32, order : memory_order) -> unit,
230
+ __yo_atomic_exchange_int :
231
+ fn(obj : *(atomic_int), desired : i32, order : memory_order) -> i32,
232
+ __yo_atomic_compare_exchange_int :
233
+ fn(
234
+ obj : *(atomic_int),
235
+ expected : *(i32),
236
+ desired : i32,
237
+ success : memory_order,
238
+ failure : memory_order
239
+ ) -> bool
240
+ );
241
+ // Phase C: size_t-specific atomic operations (for AtomicUsize)
242
+ extern(
243
+ "Yo",
244
+ __yo_atomic_load_size_t :
245
+ fn(obj : *(atomic_size_t), order : memory_order) -> usize,
246
+ __yo_atomic_store_size_t :
247
+ fn(obj : *(atomic_size_t), desired : usize, order : memory_order) -> unit,
248
+ __yo_atomic_exchange_size_t :
249
+ fn(obj : *(atomic_size_t), desired : usize, order : memory_order) -> usize,
250
+ __yo_atomic_compare_exchange_size_t :
251
+ fn(
252
+ obj : *(atomic_size_t),
253
+ expected : *(usize),
254
+ desired : usize,
255
+ success : memory_order,
256
+ failure : memory_order
257
+ ) -> bool
258
+ );
259
+ // Phase C: long-long-specific atomic operations (for AtomicI64)
260
+ extern(
261
+ "Yo",
262
+ __yo_atomic_load_llong :
263
+ fn(obj : *(atomic_llong), order : memory_order) -> i64,
264
+ __yo_atomic_store_llong :
265
+ fn(obj : *(atomic_llong), desired : i64, order : memory_order) -> unit,
266
+ __yo_atomic_exchange_llong :
267
+ fn(obj : *(atomic_llong), desired : i64, order : memory_order) -> i64,
268
+ __yo_atomic_compare_exchange_llong :
269
+ fn(
270
+ obj : *(atomic_llong),
271
+ expected : *(i64),
272
+ desired : i64,
273
+ success : memory_order,
274
+ failure : memory_order
275
+ ) -> bool
276
+ );
277
+ // Phase C: remaining atomic type wrappers
278
+ extern(
279
+ "Yo",
280
+ __yo_atomic_load_schar :
281
+ fn(obj : *(atomic_schar), order : memory_order) -> i8,
282
+ __yo_atomic_store_schar :
283
+ fn(obj : *(atomic_schar), desired : i8, order : memory_order) -> unit,
284
+ __yo_atomic_exchange_schar :
285
+ fn(obj : *(atomic_schar), desired : i8, order : memory_order) -> i8,
286
+ __yo_atomic_compare_exchange_schar :
287
+ fn(obj : *(atomic_schar), expected : *(i8), desired : i8,
288
+ success : memory_order, failure : memory_order) -> bool,
289
+ __yo_atomic_load_short :
290
+ fn(obj : *(atomic_short), order : memory_order) -> i16,
291
+ __yo_atomic_store_short :
292
+ fn(obj : *(atomic_short), desired : i16, order : memory_order) -> unit,
293
+ __yo_atomic_exchange_short :
294
+ fn(obj : *(atomic_short), desired : i16, order : memory_order) -> i16,
295
+ __yo_atomic_compare_exchange_short :
296
+ fn(obj : *(atomic_short), expected : *(i16), desired : i16,
297
+ success : memory_order, failure : memory_order) -> bool,
298
+ __yo_atomic_load_uchar :
299
+ fn(obj : *(atomic_uchar), order : memory_order) -> u8,
300
+ __yo_atomic_store_uchar :
301
+ fn(obj : *(atomic_uchar), desired : u8, order : memory_order) -> unit,
302
+ __yo_atomic_exchange_uchar :
303
+ fn(obj : *(atomic_uchar), desired : u8, order : memory_order) -> u8,
304
+ __yo_atomic_compare_exchange_uchar :
305
+ fn(obj : *(atomic_uchar), expected : *(u8), desired : u8,
306
+ success : memory_order, failure : memory_order) -> bool,
307
+ __yo_atomic_load_ushort :
308
+ fn(obj : *(atomic_ushort), order : memory_order) -> u16,
309
+ __yo_atomic_store_ushort :
310
+ fn(obj : *(atomic_ushort), desired : u16, order : memory_order) -> unit,
311
+ __yo_atomic_exchange_ushort :
312
+ fn(obj : *(atomic_ushort), desired : u16, order : memory_order) -> u16,
313
+ __yo_atomic_compare_exchange_ushort :
314
+ fn(obj : *(atomic_ushort), expected : *(u16), desired : u16,
315
+ success : memory_order, failure : memory_order) -> bool,
316
+ __yo_atomic_load_uint :
317
+ fn(obj : *(atomic_uint), order : memory_order) -> u32,
318
+ __yo_atomic_store_uint :
319
+ fn(obj : *(atomic_uint), desired : u32, order : memory_order) -> unit,
320
+ __yo_atomic_exchange_uint :
321
+ fn(obj : *(atomic_uint), desired : u32, order : memory_order) -> u32,
322
+ __yo_atomic_compare_exchange_uint :
323
+ fn(obj : *(atomic_uint), expected : *(u32), desired : u32,
324
+ success : memory_order, failure : memory_order) -> bool,
325
+ __yo_atomic_load_ullong :
326
+ fn(obj : *(atomic_ullong), order : memory_order) -> u64,
327
+ __yo_atomic_store_ullong :
328
+ fn(obj : *(atomic_ullong), desired : u64, order : memory_order) -> unit,
329
+ __yo_atomic_exchange_ullong :
330
+ fn(obj : *(atomic_ullong), desired : u64, order : memory_order) -> u64,
331
+ __yo_atomic_compare_exchange_ullong :
332
+ fn(obj : *(atomic_ullong), expected : *(u64), desired : u64,
333
+ success : memory_order, failure : memory_order) -> bool,
334
+ __yo_atomic_load_ptrdiff :
335
+ fn(obj : *(atomic_ptrdiff_t), order : memory_order) -> isize,
336
+ __yo_atomic_store_ptrdiff :
337
+ fn(obj : *(atomic_ptrdiff_t), desired : isize, order : memory_order) -> unit,
338
+ __yo_atomic_exchange_ptrdiff :
339
+ fn(obj : *(atomic_ptrdiff_t), desired : isize, order : memory_order) -> isize,
340
+ __yo_atomic_compare_exchange_ptrdiff :
341
+ fn(obj : *(atomic_ptrdiff_t), expected : *(isize), desired : isize,
342
+ success : memory_order, failure : memory_order) -> bool
343
+ );
141
344
  export(
142
345
  // Atomic types
143
346
  atomic_bool,
@@ -238,5 +441,51 @@ export(
238
441
  ATOMIC_LLONG_LOCK_FREE,
239
442
  ATOMIC_POINTER_LOCK_FREE,
240
443
  // Kill dependency
241
- kill_dependency
444
+ kill_dependency,
445
+ // Phase C (THREAD_SAFETY): int-specific atomic wrappers
446
+ __yo_atomic_load_int,
447
+ __yo_atomic_store_int,
448
+ __yo_atomic_exchange_int,
449
+ __yo_atomic_compare_exchange_int,
450
+ // Phase C: size_t atomic wrappers
451
+ __yo_atomic_load_size_t,
452
+ __yo_atomic_store_size_t,
453
+ __yo_atomic_exchange_size_t,
454
+ __yo_atomic_compare_exchange_size_t,
455
+ // Phase C: llong atomic wrappers
456
+ __yo_atomic_load_llong,
457
+ __yo_atomic_store_llong,
458
+ __yo_atomic_exchange_llong,
459
+ __yo_atomic_compare_exchange_llong,
460
+ // Phase C: signed narrow atomics
461
+ __yo_atomic_load_schar,
462
+ __yo_atomic_store_schar,
463
+ __yo_atomic_exchange_schar,
464
+ __yo_atomic_compare_exchange_schar,
465
+ __yo_atomic_load_short,
466
+ __yo_atomic_store_short,
467
+ __yo_atomic_exchange_short,
468
+ __yo_atomic_compare_exchange_short,
469
+ // Phase C: unsigned atomics
470
+ __yo_atomic_load_uchar,
471
+ __yo_atomic_store_uchar,
472
+ __yo_atomic_exchange_uchar,
473
+ __yo_atomic_compare_exchange_uchar,
474
+ __yo_atomic_load_ushort,
475
+ __yo_atomic_store_ushort,
476
+ __yo_atomic_exchange_ushort,
477
+ __yo_atomic_compare_exchange_ushort,
478
+ __yo_atomic_load_uint,
479
+ __yo_atomic_store_uint,
480
+ __yo_atomic_exchange_uint,
481
+ __yo_atomic_compare_exchange_uint,
482
+ __yo_atomic_load_ullong,
483
+ __yo_atomic_store_ullong,
484
+ __yo_atomic_exchange_ullong,
485
+ __yo_atomic_compare_exchange_ullong,
486
+ // Phase C: isize
487
+ __yo_atomic_load_ptrdiff,
488
+ __yo_atomic_store_ptrdiff,
489
+ __yo_atomic_exchange_ptrdiff,
490
+ __yo_atomic_compare_exchange_ptrdiff
242
491
  );
package/std/prelude.yo CHANGED
@@ -732,6 +732,13 @@ ComptimeToString :: trait(
732
732
  to_comptime_string : (fn(comptime(self) : Self) -> comptime(comptime_string)),
733
733
  where(Self <: Comptime)
734
734
  );
735
+ // SAFETY: All builtin primitive types (unit, void, comptime_int,
736
+ // comptime_float, comptime_string, bool, i8/i16/i32/i64, u8/u16/u32/u64,
737
+ // f32/f64, isize/usize, char/int/uint/short/ushort/long/ulong/longlong/
738
+ // ulonglong/longdouble) are plain value types with no heap allocations
739
+ // or thread-local state. They are trivially safe to send across threads
740
+ // and cannot form reference cycles. For these types, Send and Acyclic are
741
+ // structural properties of the underlying C types.
735
742
  // === Builtin types ===
736
743
  /// unit
737
744
  impl(unit, Acyclic());
@@ -5475,6 +5482,11 @@ impl(
5475
5482
  _longdouble :: longdouble;
5476
5483
  export(longdouble : _longdouble);
5477
5484
  /// Ptr
5485
+ // SAFETY: A raw pointer *(T) is Send only when the pointee T is Send —
5486
+ // the pointer itself is a plain value (an address), and Send implies
5487
+ // that any T reachable through it is safe to access from another thread.
5488
+ // *(T) is unconditionally Acyclic because raw pointers do not participate
5489
+ // in Yo's ARC reference-counting cycle analysis.
5478
5490
  impl(forall(T : Type), where(T <: Send), *(T), Send());
5479
5491
  impl(forall(T : Type), *(T), Acyclic());
5480
5492
  impl(forall(T : Type), where(T <: Comptime), *(T), Comptime());
@@ -5536,6 +5548,10 @@ impl(
5536
5548
  )
5537
5549
  );
5538
5550
  /// Array
5551
+ // SAFETY: Array(T, U) is a fixed-size array value type. It is Send when T
5552
+ // is Send because an array value is copied across threads (each thread
5553
+ // gets an independent copy). It is unconditionally Acyclic because arrays
5554
+ // are value-typed — they do not participate in ARC reference counting.
5539
5555
  impl(forall(T : Type, U : usize), where(T <: Send), Array(T, U), Send());
5540
5556
  impl(forall(T : Type, U : usize), Array(T, U), Acyclic());
5541
5557
  impl(forall(T : Type, U : usize), where(T <: Comptime), Array(T, U), Comptime());
@@ -5619,6 +5635,12 @@ impl(
5619
5635
  )
5620
5636
  );
5621
5637
  /// Slice
5638
+ // SAFETY: Slice(T) is a value-typed view (pointer + length) over a backing
5639
+ // array. Sending a slice across threads copies the header; both threads
5640
+ // can independently read the backing array. The backing array's lifetime
5641
+ // is managed by its owning collection (which must itself be Send to cross
5642
+ // threads). Slice is always Acyclic — it is a value type with no ARC.
5643
+ impl(forall(T : Type), where(T <: Send), Slice(T), Send());
5622
5644
  impl(forall(T : Type), Slice(T), Acyclic());
5623
5645
  impl(forall(T : Type), where(T <: Comptime), Slice(T), Comptime());
5624
5646
  impl(forall(T : Type), where(T <: Runtime), Slice(T), Runtime());
@@ -7499,13 +7521,21 @@ export(
7499
7521
  box
7500
7522
  );
7501
7523
  /// Iso — an isolated reference-counted value that can be sent across threads.
7524
+ // SAFETY: Iso(T) is unconditionally Send (no `T <: Send` bound) because
7525
+ // Iso's public API exposes no operation on the inner T except extract(),
7526
+ // which atomically verifies rc == 1 before returning. If you add any new
7527
+ // method to Iso(T) that touches the inner T (map, peek, read, &self
7528
+ // borrow, etc.), this Send claim becomes unsound and must be revoked.
7529
+ // See plans/THREAD_SAFETY.md "Iso(T) design notes — precedent and known
7530
+ // risks" for the full argument.
7502
7531
  impl(forall(T : Type), Iso(T), Send());
7503
7532
  impl(forall(T : Type), where(T <: Acyclic), Iso(T), Acyclic());
7504
7533
  impl(
7505
7534
  forall(T : Type),
7506
7535
  Iso(T),
7507
- /// Attempt to extract the inner value if this `Iso` holds the only reference.
7508
- extract : (fn(self : Self) -> Option(T))(
7536
+ /// Extract the inner value if this `Iso` holds the only reference.
7537
+ /// Panics if rc != 1 or if this Iso has already been extracted.
7538
+ extract : (fn(self : Self) -> T)(
7509
7539
  __yo_iso_extract(self)
7510
7540
  )
7511
7541
  );
@@ -7581,6 +7611,9 @@ Arc :: (fn(comptime(V) : Type, where(V <: (Send, Acyclic))) -> comptime(Type))(
7581
7611
  );
7582
7612
  /// Allocate a value inside an Arc, returning an `Arc(V)`.
7583
7613
  arc :: (fn(forall(V : Type), own(value) : V, where(V <: (Send, Acyclic))) -> Arc(V))(Arc(V)(value));
7614
+ // SAFETY: Arc(T) is an atomic reference-counted wrapper. It is Send when
7615
+ // the inner T is Send (all fields of the atomic-object must be Send per
7616
+ // auto-derive) and Acyclic (prevents permanent leaks from Arc cycles).
7584
7617
  impl(forall(T : Type), where(T <: (Send, Acyclic)), Arc(T), Send());
7585
7618
  export(Arc, arc);
7586
7619
  /// === MaybeUninit ===
@@ -8325,6 +8358,8 @@ FutureState :: enum(
8325
8358
  /// The future was aborted (e.g., via `unwind`).
8326
8359
  Aborted = -(2)
8327
8360
  );
8361
+ // SAFETY: FutureState is a plain enum of integer values (Pending, Done,
8362
+ // Aborted) with no heap allocations. It is trivially Send and Acyclic.
8328
8363
  impl(FutureState, Acyclic());
8329
8364
  impl(FutureState, Runtime());
8330
8365
  impl(FutureState, Send());
@@ -8345,6 +8380,10 @@ JoinHandle :: (fn(comptime(T) : Type) -> comptime(Type))(
8345
8380
  __future : *(T)
8346
8381
  )
8347
8382
  );
8383
+ // Phase L (THREAD_SAFETY): JoinHandle is NOT Send — the inner future state
8384
+ // machine lives on the spawner's event-loop thread. Cross-thread result
8385
+ // delivery uses Channel(T).
8386
+ impl(forall(T : Type), JoinHandle(T), !(Send()));
8348
8387
  export(JoinHandle);
8349
8388
  /// Io module — the async runtime effect.
8350
8389
  ///
@@ -8360,6 +8399,10 @@ Io :: struct(
8360
8399
  /// Spawn a `Future` as an independent task, returning a `JoinHandle`.
8361
8400
  spawn : (fn(forall(T : Type, E : Type.Struct), fut : Impl(Future(T, E)), e : E) -> JoinHandle(T))
8362
8401
  );
8402
+ // Phase L (THREAD_SAFETY): Io is NOT Send — the async runtime it represents
8403
+ // is per-thread (per AGENTS.md). Sending Io across a thread boundary would
8404
+ // reference a scheduler on the wrong thread.
8405
+ impl(Io, !(Send()));
8363
8406
  export(Io);
8364
8407
  extern(
8365
8408
  "Yo",
@@ -1,4 +1,5 @@
1
1
  //! Unicode code point type (`rune`) for representing individual characters.
2
+ pragma(Pragma.AllowUnsafe);
2
3
  /// Unicode code point (U+0000 to U+10FFFF, excluding surrogates).
3
4
  /// Similar to Go's `rune` or Rust's `char`.
4
5
  rune :: newtype(
@@ -93,5 +94,8 @@ impl(
93
94
  (>=) : ((a, b) -> (a.char >= b.char))
94
95
  )
95
96
  );
97
+ // SAFETY: rune is a newtype over u32. u32 is a plain value type with no
98
+ // internal heap allocations or thread-local state — it is trivially safe
99
+ // to send across threads.
96
100
  impl(rune, Send());
97
101
  export(rune);