Skip to content

Conversation

zjp-CN
Copy link

@zjp-CN zjp-CN commented Jul 31, 2025

Summary

This RFC introduces a concise safety-comment convention for unsafe code in standard libraries:
tag every public unsafe function with #[safety::requires] and call with #[safety::checked].

Safety tags refine today’s safety-comment habits: a featherweight syntax that condenses every
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.

#[safety::requires( // 💡 define safety tags on an unsafe function
    valid_ptr = "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
        valid_ptr, aligned, initialized = "optional reason"
    )]
    unsafe { read(&()) };
}

Rendered

@zjp-CN zjp-CN requested a review from ia0 August 1, 2025 06:44
Co-authored-by: kennytm <kennytm@gmail.com>
@jswrenn jswrenn mentioned this pull request Aug 7, 2025
Copy link

@danjl1100 danjl1100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the direction this is moving with requires and checked since the initial version.

@madsmtm madsmtm added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 22, 2025
@madsmtm madsmtm added T-libs-api Relevant to the library API team, which will review and decide on the RFC. T-rustdoc Relevant to rustdoc team, which will review and decide on the RFC. labels Aug 22, 2025
@Alexendoo
Copy link
Member

rust-lang/rust-clippy#11600 was a related effort on the clippy side that stalled on inactivity, though not specific to unsafe

@zjp-CN
Copy link
Author

zjp-CN commented Sep 7, 2025

rust-lang/rust-clippy#11600 was a related effort on the clippy side that stalled on inactivity, though not specific to unsafe

Thanks for the link. I’ve expanded the “Alternatives” section with a side-by-side comparison.

@zjp-CN
Copy link
Author

zjp-CN commented Sep 16, 2025

I've left some comments in rust-for-linux channel on zulip:

we've proposed this RFC safety tags (structured safety comments but in tool attribute syntax) in rust-lang repo to achieve the following goals:

  1. annotate safety properties on public unsafe APIs in libcore/libstd: downsteam crates like R4L will benefit from these tags
  2. tag checking will be implemented in clippy: R4L will just gain the SP checks out of the box; we'd like to implement and maintain such feature in clippy, but currently it seems no interest from clippy and R4L: #clippy > Discussion with R4L
  3. support tag LSP in Rust-Analyzer to have hover-doc, jump-to-definition, and auto-completion on safety tags for better dev-ex
  4. generate safety API documentation from safety tags to avoid replication: we had a small chat with @GuillaumeGomez in RustChinaConf2025, and he'd like to have rustdoc recognize such safety attributes in rendering

src: #rust-for-linux > Provide the meaning of "Valid" to Understand. @ 💬


#[safety::checked(
aligned = "alignment of ptr has be adjusted",
valid_ptr, initialized = "delegated to the caller"
Copy link
Author

@zjp-CN zjp-CN Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(As an enhanced example from my comment in #3458 (comment) of RFC unsafe fields )

I'm wondering that there should be a specific #[safety::delegated] attribute the same as #[safety::checked], but more handy and concise to use:

#[safety::requires(
  // bad comments by mentioning things in release build, but that's what the current implementation is in libstd
  no_more_than_cap = "Caller must ensure the new_len <= cap (in release mode)",
  valid_T_all_in_length_range = "Please also make sure all T in 0..=new_len are valid, since this call can't ensure this!"
)]
unsafe fn set_len(&mut self, new_len: usize) {
  debug_assert!(new_len <= self.capacity());
  #[safety::delegated(
    no_more_than_cap, valid_T_all_in_length_range
  )]
  unsafe { self.len = new_len }
}

Tags in delegated are merged into checked, so both can coexist and work well.


What I mean handy is that we can omit definition texts in such context, because safety requirements are first introduced on unsafe field declaration or unsafe function being called, so it's verbose to duplicate them when without extra context attached:

struct Vec<T> {
  #[safety::requires(
    no_more_than_cap = "0 <= len <= cap",
    valid_T_all_in_length_range = "all elements in the Vec<T> between 0 and len are valid T"
  )]
  unsafe len: usize,
  ...
}

// The same safety requirements as on `unsafe len` field are rendered here
// because safety tags need support from rustdoc.
#[safety::requires(no_more_than_cap, valid_T_all_in_length_range)]
unsafe fn set_len(&mut self, new_len: usize) {
  debug_assert!(new_len <= self.capacity());
  #[safety::delegated(no_more_than_cap, valid_T_all_in_length_range)]
  unsafe { self.len = new_len }
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason to have #[safety::delegated] is that #[safety::requires] must incorporate all tags delegated from the fn body. Must means that a diagnostic happens if such one tag is missing.

checked won't have such delegation checks when listing delegated tags in requires.

Copy link
Author

@zjp-CN zjp-CN Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paste a totally semantic-unsafety-oriented example given in my comment on RFC unsafe fields:

struct Vec<T> {
  #[safety::requires(any { 
    good_on_read,
    hazard_on_mut(valid_T_all_in_length_range, no_more_than_cap)
  })]
  unsafe len: usize,
}

pub fn len(&self) -> usize {
    #[safety::checked(good_on_read)]
    unsafe { self.len }
}

#[safety::requires(hazard_on_mut)]
unsafe fn set_len(&mut self, new_len: usize) {
  #[safety::delegated(hazard_on_mut)]
  unsafe { self.len = new_len }
}

This is an improvement to #3842 (comment) as well.

(I don't really suggest supporting safety tags beyond visual unsafe operations too far though.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should at least add the unsafe back to the len field, I don't think it is supposed that #[safety::requires] can be applicable on things declared safe.

Copy link
Author

@zjp-CN zjp-CN Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snippet is more to clippy PR danger_not_accepted.

Safety tags for unsafe fields are already stated to be supported.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zjp-CN

i mean your declaration of len is not an unsafe field at all, not whether about the attribute supports unsafe field or not.

struct Vec<T> {
  #[safety::requires(any { 
    good_on_read,
    hazard_on_mut(valid_T_all_in_length_range, no_more_than_cap)
  })]
  len: usize,
//^ NO UNSAFE IN SIGHT
}

Copy link
Author

@zjp-CN zjp-CN Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

danger_not_accepted doesn't require unsafe.

But to avoid further unnecessary misinterpretation, I've just modified it the way you asked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. T-rustdoc Relevant to rustdoc team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.