Skip to content

Commit 66eda21

Browse files
committed
future: #[safety::batch_checked] Shares Tag Discharging
1 parent 23f8942 commit 66eda21

File tree

1 file changed

+66
-1
lines changed

1 file changed

+66
-1
lines changed

text/0000-safety-tags.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ Our proposed syntax looks closer to structured comments:
525525

526526
```rust
527527
#[safety::checked(
528-
valid_ptr, align, initialized = "`self.head_tail()` returns two slices to live elements.",
528+
valid_ptr, aligned, initialized = "`self.head_tail()` returns two slices to live elements.",
529529
not_owned = "because we incremented...",
530530
)]
531531
unsafe { ptr::read(elem) }
@@ -610,6 +610,71 @@ following cases:
610610
But we believe safety requirements are almost mostly imposed by unsafe functions, so tagging a
611611
struct, enum, or union is neither needed nor permitted.
612612

613+
## `#[safety::batch_checked]` Shares Tag Discharging
614+
615+
Discharging the same tags simultaneously can be convenient. However, supporting this means that tags
616+
in `checked` are applied to multiple unsafe operations. As a result, obligations are discharged
617+
extensively across various contexts, including nested and chained calls, different calls, as well as
618+
repeated calls with different values. This could lead to confusion and potential misuse, making it
619+
unclear what has been checked in shared mode or single mode. Partial discharging can be dangerous.
620+
621+
I believe it is less error-prone to let `safety::checked` handle atomic discharging. Therefore, we
622+
should introduce a new attribute named `safety::batch_checked` to support the discharging of shared
623+
tags. This approach allows us to distinguish between different discharging semantics through
624+
different syntaxes.
625+
626+
```rust
627+
#[safety::batch_checked( // automatically merged this tag into `checked` if it's required
628+
aligned = "the place is aligned correctly for i32 by providing correct layout above"
629+
)]
630+
unsafe {
631+
#[safety::checked(
632+
valid_for_reads = "the place is newly allocated, so we have exclusive ownership of it"
633+
)]
634+
ptr.write(42);
635+
636+
#[safety::checked(
637+
valid_for_reads = "we have exclusive ownership",
638+
initialized = "just initialized above"
639+
)]
640+
assert_eq!(ptr.read(), 42);
641+
}
642+
643+
#[safety::batch_checked(
644+
aligned = "arrays are properly aligned",
645+
valid_for_reads = "the arrays are owned by this function, and contain the copy type f32",
646+
)]
647+
unsafe {
648+
float32x4x4_t(
649+
vld1q_f32(a.as_ptr()),
650+
vld1q_f32(b.as_ptr()),
651+
vld1q_f32(c.as_ptr()),
652+
vld1q_f32(d.as_ptr()),
653+
)
654+
}
655+
```
656+
657+
Atomic discharging saves us from visual unsafe granularity, and focus on semantic unsafe
658+
granularity, because any use of `checked` is only valid for single unsafe operation.
659+
660+
```rust
661+
#[safety::checked(...)] // ❌ hard error
662+
unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
663+
664+
#[safety::batch_checked(...)] //
665+
unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
666+
667+
unsafe {
668+
#[safety::checked(...)] // ✅ dereferencing raw pointer is an unsafe operation in future possibilities
669+
let int_value = *ptr.cast::<u32>();
670+
#[safety::checked(...)] //
671+
char::from_u32_unchecked(int_value)
672+
}
673+
```
674+
675+
we could have `#[deny(clippy::batch_checked)]` to prohibit any use of `#[safety::batch_checked]` in
676+
crates if rigid atomic discharging is required.
677+
613678
# Future possibilities
614679
[future-possibilities]: #future-possibilities
615680

0 commit comments

Comments
 (0)