Skip to content
Open
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5d92060
RFC: safety-tags
zjp-CN Jul 31, 2025
7ccafe1
safety-tags: styling
zjp-CN Jul 31, 2025
c0495e3
safety-tags: add previous zulipchat link on Safety Property System
zjp-CN Jul 31, 2025
2004fd4
safety-tags: add tag discharge of unsafe encapsulation
zjp-CN Jul 31, 2025
0090419
safety-tags: typo
zjp-CN Jul 31, 2025
4b6bdb2
safety-tags: styling
zjp-CN Jul 31, 2025
567b998
safety-tags: fix link
zjp-CN Aug 1, 2025
e9ec420
safety-tags: clarify semver rules
zjp-CN Aug 1, 2025
3da20c9
safety-tags: #[ref] needs clippy reporting
zjp-CN Aug 1, 2025
719342e
safety-tags: #![clippy::safety] => #![clippy::safety::use]
zjp-CN Aug 1, 2025
421ea8d
safety-tags: fix tag in loop snippet
zjp-CN Aug 1, 2025
a0294a8
safety-tags: clippy::safety::use => clippy::safety::r#use
zjp-CN Aug 1, 2025
39dec42
Update text/0000-safety-tags.md
zjp-CN Aug 1, 2025
e2e5a11
safety-tags: Should Tags Take Arguments?
zjp-CN Aug 1, 2025
80cf23d
safety-tags: rephrase motivations
zjp-CN Aug 1, 2025
392d5fc
safety-tags: replace clippy namespace with safety namesapce
zjp-CN Aug 2, 2025
88c3525
safety-tags: alternative named argument syntax
zjp-CN Aug 2, 2025
731a5ba
safety-tag: Rationale for the Proposed Implementation
zjp-CN Aug 2, 2025
39b17e2
safety-tags: teams support for this RFC
zjp-CN Aug 2, 2025
2130878
safety-tags: Why Not Structured Safety Comments?
zjp-CN Aug 2, 2025
b077573
safety-tags: Encapsulate Declaration with define_safety_tag!
zjp-CN Aug 2, 2025
d906447
safety-tags: minor clarification
zjp-CN Aug 3, 2025
078d22c
safety-tags: summarize safety attributes with 💡
zjp-CN Aug 3, 2025
6f9577d
safety-tags: explain how safety::checked is validated on unsafe opera…
zjp-CN Aug 3, 2025
7e0daff
safety-tags: minor adjustment
zjp-CN Aug 3, 2025
cb7dbd7
safety-tags: semantic granularity with future entity-reference
zjp-CN Aug 3, 2025
ad2d8d7
safety-tags: minor fixes
zjp-CN Aug 3, 2025
35aa734
safety-tags: clarify #[ref] support from clippy and RA
zjp-CN Aug 1, 2025
82a9c2c
remove safety::import; named arguments in safety::{requires,checked}
zjp-CN Aug 4, 2025
75f85f3
add "Tagging More Unsafe Operations"
zjp-CN Aug 4, 2025
2711e17
minor fixes
zjp-CN Aug 4, 2025
33826b1
force description in tag definitions; fix `:` by `=`
zjp-CN Aug 5, 2025
3aa44b4
add @deprecated disgnostics; major change to add or remove tags
zjp-CN Aug 5, 2025
27a7b0d
minor fix
zjp-CN Aug 5, 2025
5dffa30
remove `@deprecated`; swap the order on Versioned invariants and Sema…
zjp-CN Aug 6, 2025
6d0d064
minor fix
zjp-CN Aug 6, 2025
c73221b
clarify semver on definition change
zjp-CN Aug 6, 2025
21e5431
Auto Generate Safety Docs from Tags; multiple attrs on the same node
zjp-CN Aug 6, 2025
2fb3d0c
minor fixes and clarification on semver
zjp-CN Aug 8, 2025
bfb6304
minor fixes: discharge of undefined tags warns by default
zjp-CN Aug 8, 2025
0405d98
minor fix
zjp-CN Aug 8, 2025
12c7f89
review: request Lang team to reserve `safety` namespace
zjp-CN Aug 23, 2025
b95cb59
syntax: only accepy `()` delemiter around tags
zjp-CN Aug 23, 2025
e3d43ae
`checked` is lenient in diagnostics by default; upgrade in cargo check
zjp-CN Aug 23, 2025
93e48b9
tag styles: visually group tags; naming convention
zjp-CN Aug 23, 2025
d4d0679
rephrase team approvals
zjp-CN Aug 24, 2025
4c8a256
entity-reference attribute candidates due to `ref` being a keyword
zjp-CN Aug 24, 2025
23f8942
tag: `snake_case` naming convention and render to `Snake case`
zjp-CN Aug 24, 2025
66eda21
future: `#[safety::batch_checked]` Shares Tag Discharging
zjp-CN Aug 24, 2025
4aaba41
chore: typos
zjp-CN Sep 7, 2025
3ac2da3
alternativs: compared to Clippy lint `danger_not_accepted`
zjp-CN Sep 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 35 additions & 37 deletions text/0000-safety-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ requirement into a single, check-off reminder.
The following snippet [compiles] today if we enable enough nightly features, but we expect Clippy
and Rust-Analyzer to enforce tag checks and provide first-class IDE support.

[compiles]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=322dbd93610aca05db49382802c732c3
[compiles]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=34c1b3d4c13685bae6da6eb299fded95

```rust
#[safety::requires { // 💡 define safety tags on an unsafe function
#[safety::requires( // 💡 define safety tags on an unsafe function
ValidPtr = "src must be [valid](https://doc.rust-lang.org/std/ptr/index.html#safety) for reads",
Aligned = "src must be properly aligned, even if T has size 0",
Initialized = "src must point to a properly initialized value of type T"
}]
)]
pub unsafe fn read<T>(ptr: *const T) { }

fn main() {
#[safety::checked { // 💡 discharge safety tags on an unsafe call
#[safety::checked( // 💡 discharge safety tags on an unsafe call
ValidPtr, Aligned, Initialized = "optional reason"
}]
)]
unsafe { read(&()) };
}
```
Expand Down Expand Up @@ -130,11 +130,9 @@ propose that `#[safety]` be implicitly registered for every crate.
Syntax of a safety tag is defined as follows:

```text
SafetyTag -> `#` `[` `safety::` Operation Object `]`
SafetyTags -> `#` `[` `safety::` Operation `(` Tags `)` `]`

Operation -> requires | checked

Object -> `[` Tags `]` | `(` Tags `)` | `{` Tags `}`
Operation -> `requires` | `checked`

Tags -> Tag (`,` Tag)* `,`?

Expand All @@ -150,15 +148,15 @@ Take [`ptr::read`] as an example: its safety comment lists three requirements, s
corresponding tags on the function declaration and mark each one off at the call site.

Note that a tag definition must contain human text to describe safety requirements for readers to
understand them and Clippy to emit good error messages.
understand them and Clippy to emit good diagnostic messages.

```rust
#[safety::requires { // defsite or definition
#[safety::requires( // defsite or definition
ValidPtr = "definition1", Aligned = "definition2", Initialized = "definition3"
}]
)]
pub unsafe fn read<T>(ptr: *const T) -> T { ... }

#[safety::checked { ValidPtr, Aligned, Initialized }] // callsite or discharge
#[safety::checked( ValidPtr, Aligned, Initialized )] // callsite or discharge
unsafe { read(ptr) };
```

Expand All @@ -167,14 +165,14 @@ We can also attach comments for a tag to clarify how safety requirements are met
```rust
for _ in 0..n {
unsafe {
#[safety::checked { ValidPtr, Aligned, Initialized =
#[safety::checked(ValidPtr, Aligned, Initialized =
"addr range p..p+n is properly initialized from aligned memory"
}]
)]
c ^= p.read();

#[safety::checked { InBounded, ValidNum =
#[safety::checked(InBounded, ValidNum =
"`n` won't exceed isize::MAX here, so `p.add(n)` is fine"
}]
)]
p = p.add(1);
}
}
Expand Down Expand Up @@ -212,19 +210,19 @@ Now consider forwarding invariants of unsafe callees onto the unsafe caller for
propogation:

```rust
#[safety::requires { ValidPtr = "...", Aligned = "...", Initialized = "..." }]
#[safety::requires(ValidPtr = "...", Aligned = "...", Initialized = "...")]
unsafe fn propogation<T>(ptr: *const T) -> T {
#[safety::checked { ValidPtr, Aligned, Initialized }]
#[safety::checked(ValidPtr, Aligned, Initialized)]
unsafe { read(ptr) }
}
```

Tags defined on an unsafe function must be **fully** discharged at callsites. No partial discharge:

```rust
#[safety::requires { ValidPtr = "...", Initialized = "..." }]
#[safety::requires(ValidPtr = "...", Initialized = "...")]
unsafe fn delegation<T>(ptr: *const T) -> T {
#[safety::checked { Aligned }] // 💥 Error: Tags are not fully discharged.
#[safety::checked(Aligned)] // 💥 warning: Tags are not fully discharged.
unsafe { read(ptr) }
}
```
Expand All @@ -233,16 +231,16 @@ For such partial unsafe delegations, please fully discharge tags on the callee a
tags on the caller.

```rust
#[safety::requires { ValidPtr = "...", Initialized = "..." }]
#[safety::requires(ValidPtr = "...", Initialized = "...")]
unsafe fn delegation<T>(ptr: *const T) -> T {
let align = mem::align_of::<T>();
let addr = ptr as usize;
let aligned_addr = (addr + align - 1) & !(align - 1);

#[safety::checked {
#[safety::checked(
Aligned = "alignment of ptr has be adjusted",
ValidPtr, Initialized = "delegated to the caller"
}]
)]
unsafe { read(ptr) }
}
```
Expand All @@ -252,12 +250,12 @@ invariants, and define the new tag on `delegation` function. This practice exten
delegation of multiple tag discharges:

```rust
#[safety::requires { MyInvariant = "Invariants of A and C, but could be a more contextual name." }]
#[safety::requires(MyInvariant = "Invariants of A and C, but could be a more contextual name.")]
unsafe fn delegation() {
unsafe {
#[safety::checked { A = "delegated to the caller's MyInvariant", B }]
#[safety::checked(A = "delegated to the caller's MyInvariant", B)]
foo();
#[safety::checked { C = "delegated to the caller's MyInvariant", D }]
#[safety::checked(C = "delegated to the caller's MyInvariant", D)]
bar();
}
}
Expand Down Expand Up @@ -314,11 +312,11 @@ pub const unsafe fn read<T>(src: *const T) -> T { ... }
```rust
/// # Safety
/// Behavior is undefined if any of the following conditions are violated:
#[safety::requires {
#[safety::requires(
ValidPtr = "`src` must be [valid] for reads";
Aligned = "`src` must be properly aligned. Use [`read_unaligned`] if this is not the case";
Initialized = "`src` must point to a properly initialized value of type `T`"
}]
)]
/// # Examples
pub const unsafe fn read<T>(src: *const T) -> T { ... }
```
Expand Down Expand Up @@ -516,10 +514,10 @@ There are alternative discussion or Pre-RFCs on IRLO:
Our proposed syntax looks closer to structured comments:

```rust
#[safety::checked {
#[safety::checked(
ValidPtr, Align, Initialized = "`self.head_tail()` returns two slices to live elements.",
NotOwned = "because we incremented...",
}]
)]
unsafe { ptr::read(elem) }
```

Expand Down Expand Up @@ -565,16 +563,16 @@ We could allow *any* arguments in tag usage without validation. Tag arguments wo
description of an unsafe operation, but they are never type checked. An example:

```rust
#[safety::requires {
#[safety::requires(
ValidPtr = {
args = [ "p", "T", "len" ],
desc = "pointer `{p}` must be valid for \
reading and writing the `sizeof({T})*{n}` memory from it"
}
}]
)]
unsafe fn foo<T>(ptr: *const T) -> T { ... }

#[safety::checked { ValidPtr(p) }] // p will not be type-checked
#[safety::checked(ValidPtr(p))] // p will not be type-checked
unsafe { bar(p) }
```

Expand Down Expand Up @@ -615,7 +613,7 @@ discharge either `DropCheck` or `CopyType` at the call site, depending on the co

Another instance is `<*const T>::as_ref`, whose safety doc states that the caller must guarantee
“the pointer is either null or safely convertible to a reference”. This can be expressed as
`#[safety::requires { any = { Null, ValidPtr2Ref } }]`, allowing the caller to discharge whichever
`#[safety::requires(any = { Null, ValidPtr2Ref })]`, allowing the caller to discharge whichever
tag applies.

## Entity References and Code Review Enhancement
Expand All @@ -641,10 +639,10 @@ fn try_fold<B, F, R>(&mut self, mut init: B, mut f: F) -> R
guard.consumed += 1;

#[safety::ref(try_fold)] // 💡
#[safety::checked { ValidPtr, Aligned, Initialized, DropCheck =
#[safety::checked(ValidPtr, Aligned, Initialized, DropCheck =
"Because we incremented `guard.consumed`, the deque \
effectively forgot the element, so we can take ownership."
}]
)]
unsafe { ptr::read(elem) }
})
.try_fold(init, &mut f)?;
Expand Down