prudent helps you minimize/isolate parts of Rust unsafe expressions/statements.
- ergonomic (as much as possible)
- lightweight (no dependencies, no procedural macros - fast build,
rust-analyzer-friendly) - zero-cost (for binary size, speed and memory), verified in compile time
Following are all the positive examples. They are also run by the above GitHub Actions as doctests.
For negative examples, see documentation of each prudent macro.
use prudent::unsafe_fn;
const unsafe fn unsafe_fn_no_args() {}
const unsafe fn unsafe_fn_one_arg(b: bool) -> bool { b }
const unsafe fn unsafe_fn_two_args(_: bool, u: u8) -> u8 { u }
const _: () = unsafe_fn!(unsafe_fn_no_args);
const _: bool = unsafe_fn!(unsafe_fn_one_arg => true);
const _: u8 = unsafe_fn!(unsafe_fn_two_args => true, 0);use prudent::unsafe_method;
const _: u8 = unsafe_method!( 1u8 =>. unchecked_add => 0 );use prudent::unsafe_method;
struct SNonCopy {}
impl SNonCopy {
unsafe fn unsafe_method_no_args(&self) {}
unsafe fn unsafe_method_one_arg(&self, _: bool) {}
unsafe fn unsafe_method_two_args(&self, _: bool, _: bool) {}
}
fn main() {
let s = SNonCopy {};
unsafe_method!(s =>. unsafe_method_no_args);
unsafe_method!(s =>. unsafe_method_one_arg => true);
unsafe_method!(s =>. unsafe_method_two_args => true, false);
}use prudent::unsafe_method;
struct SNonCopy {}
impl SNonCopy {
unsafe fn unsafe_method_no_args(&mut self) {}
unsafe fn unsafe_method_one_arg(&mut self, _: bool) {}
unsafe fn unsafe_method_two_args(&mut self, _: bool, _: bool) {}
}
fn main() {
let mut s = SNonCopy {};
unsafe_method!(s =>. unsafe_method_no_args);
unsafe_method!(s =>. unsafe_method_one_arg => true);
unsafe_method!(s =>. unsafe_method_two_args => true, false);
}use prudent::unsafe_method;
fn main() {
{
struct SNonCopy {}
impl SNonCopy {
unsafe fn unsafe_method_no_args(self) {}
unsafe fn unsafe_method_one_arg(self, _: bool) {}
unsafe fn unsafe_method_two_args(self, _: bool, _: bool) {}
}
unsafe_method!(SNonCopy {} =>. unsafe_method_no_args);
unsafe_method!(SNonCopy {} =>. unsafe_method_one_arg => true);
unsafe_method!(SNonCopy {} =>. unsafe_method_two_args => true, false);
}
{
#[derive(Clone, Copy)]
struct SCopy {}
impl SCopy {
unsafe fn unsafe_method_no_args(self) {}
}
let sCopy = SCopy {};
unsafe_method!(sCopy =>. unsafe_method_no_args);
unsafe_method!(sCopy =>. unsafe_method_no_args);
let _ = sCopy;
}
}use prudent::unsafe_ref;
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: &bool = unsafe_ref!(PT);
fn main() {
let _ = unsafe_ref!(PT);
}use prudent::unsafe_ref;
const BS: [bool; 2] = [true, false];
const PT: *const [bool] = &BS as *const [bool];
const _: &[bool] = unsafe_ref!(PT);
fn main() {
let _ = unsafe_ref!(PT);
}use prudent::unsafe_ref;
# use core::fmt::Display;
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: &dyn Display = unsafe_ref!(PT);
fn main() {}use prudent::unsafe_ref;
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: &'static bool = unsafe_ref!(PT, 'static);
fn main() {
let _ = unsafe_ref!(PT, 'static);
}use prudent::unsafe_ref;
use core::fmt::Display;
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: &'static dyn Display = unsafe_ref!(PT, 'static);
fn main() {}use prudent::unsafe_ref;
const BS: [bool; 2] = [true, false];
const PT: *const [bool] = &BS as *const [bool];
const _: &'static [bool] = unsafe_ref!(PT, 'static);
fn main() {
let _ = unsafe_ref!(PT, 'static);
}use prudent::unsafe_ref;
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: &bool = unsafe_ref!(PT, bool);
fn main() {
let _ = unsafe_ref!(PT, bool);
}use prudent::unsafe_ref;
const BS: [bool; 2] = [true, false];
const PT: *const [bool] = &BS as *const [bool];
const _: &[bool] = unsafe_ref!(PT, [bool]);
fn main() {
let _ = unsafe_ref!(PT, [bool]);
}use prudent::unsafe_ref;
# use core::fmt::Display;
const B: bool = true;
const PT: *const dyn Display = &B as *const dyn Display;
const _: &dyn Display = unsafe_ref!(PT, dyn Display);
fn main() {
let _ = unsafe_ref!(PT, dyn Display);
}use prudent::unsafe_mut;
fn main() {
let mut b: bool = true;
let pt: *mut bool = &mut b as *mut bool;
let _: &bool = unsafe_mut!(pt);
let _ = unsafe_mut!(pt);
}use prudent::unsafe_mut;
fn main() {
let mut bs: [bool; 2] = [true, false];
let pt: *mut [bool] = &mut bs as *mut [bool];
let _: &[bool] = unsafe_mut!(pt);
let _ = unsafe_mut!(pt);
}use prudent::unsafe_mut;
# use core::fmt::Display;
fn main() {
let mut b: bool = true;
let pt: *mut bool = &mut b as *mut bool;
let _: &mut dyn Display = unsafe_mut!(pt);
let _: &dyn Display = unsafe_mut!(pt);
}use prudent::unsafe_mut;
fn main() {
let b: &'static mut bool = Box::leak( Box::new(true) );
let pt: *mut bool = b as *mut bool;
let _: &'static mut bool = unsafe_mut!(pt, 'static);
let _ = unsafe_mut!(pt, 'static);
# let _drop_for_miri = unsafe { Box::from_raw(b) };
}use prudent::unsafe_mut;
# use core::fmt::Display;
fn main() {
let b: &'static mut bool = Box::leak( Box::new(true) );
let pt: *mut bool = b as *mut bool;
let _: &'static mut dyn Display = unsafe_mut!(pt, 'static);
let _ = unsafe_mut!(pt, 'static);
# let _drop_for_miri = unsafe { Box::from_raw(b) };
}use prudent::unsafe_mut;
fn main() {
let bs: &'static mut [bool] = Box::leak( Box::new([true, false]) );
let pt: *mut [bool] = bs as *mut [bool];
let _: &'static mut [bool] = unsafe_mut!(pt, 'static);
let _ = unsafe_mut!(pt, 'static);
# let _drop_for_miri = unsafe { Box::from_raw(bs) };
}use prudent::unsafe_mut;
fn main() {
let mut b: bool = true;
let pt: *mut bool = &mut b as *mut bool;
let _: &mut bool = unsafe_mut!(pt, bool);
let _ = unsafe_mut!(pt, bool);
}use prudent::unsafe_mut;
fn main() {
let bs: &'static mut [bool] = Box::leak( Box::new([true, false]) );
let pt: *mut [bool] = bs as *mut [bool];
let _: &mut [bool] = unsafe_mut!(pt, [bool]);
let _ = unsafe_mut!(pt, [bool]);
# let _drop_for_miri = unsafe { Box::from_raw(bs) };
}use prudent::unsafe_mut;
# use core::fmt::Display;
fn main() {
let mut b: bool = true;
let pt: *mut dyn Display = &mut b as *mut dyn Display;
let _: &mut dyn Display = unsafe_mut!(pt, dyn Display);
let _ = unsafe_mut!(pt, dyn Display);
}Only for types that implement/derive core::marker::Copy.
use prudent::unsafe_val;
fn main() {
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: bool = unsafe_val!(PT);
let _ = unsafe_val!(PT);
}use prudent::unsafe_val;
fn main() {
const B: bool = true;
const PT: *const bool = &B as *const bool;
const _: bool = unsafe_val!(PT, bool);
let _ = unsafe_val!(PT, bool);
}use prudent::unsafe_set;
fn main() {
let mut b: bool = true;
let pt: *mut bool = &mut b as *mut bool;
unsafe_set!(pt, false);
unsafe_set!(pt, true);
}use prudent::unsafe_set;
struct SNonCopy {}
fn main() {
let mut s: SNonCopy = SNonCopy {};
let pt: *mut SNonCopy = &mut s as *mut SNonCopy;
let setFrom: SNonCopy = SNonCopy {};
unsafe_set!(pt, setFrom);
let setFrom: SNonCopy = SNonCopy {};
unsafe_set!(pt, setFrom);
}use prudent::unsafe_static_set;
static mut B: bool = true;
fn main() {
unsafe_static_set!(B, false);
}Results of prudent's macro invocations are const (if the original invocation/expression would
also be const).
GitHub Actions runs on Alpine Linux (without libc). See the
reports.
cargo testcargo clippyfor lintingMIRI
- Error code validation: Where possible, expected error numbers are validated with
cargo +nightly test, (The Rustdoc book > Unstable features > Error numbers for compile-fail doctests). The error codes are validated by GitHub Actions, see results. Error code validation requiresnightlyRust toolchain. See alsosrc/linted_with_tests.rsfor expected compilation error codes. - Error output validation: Some lint violations don't have a special error code. So we validate the error message in violations_coverage/verify_error_messages/ with dtolnay/trybuild crates.io/crates/trybuild.
TODO
prudent helps both authors, reviewers and all of us:
- Authors/maintainers:
- Notice/prevent accidental (unintended), or unnecessary,
unsafecode:- function/method calls:
- in parameters to calls to an
unsafefunction or method - in an expression that evaluates to an (
unsafe) function (that is to be evaluated) - in an expression that evaluates to the receiver (
self) of anunsafemethod
- in parameters to calls to an
- variable access:
- TODO:
static mutvariables - TODO: fields of
uniontypes
- TODO:
- value cast (to a different type):
- TODO: in expressions whose deref is
unsafe
- TODO: in expressions whose deref is
- function/method calls:
- Notice/prevent accidental (unintended), or unnecessary,
- Reviewers: Save your time by making the unsafe parts shorter. Focus on what matters.
- All of us:
- Prevent accidental invocation of functions (3rd party, or even your own) that
- have been called as a part of (larger)
unsafe {...}block, and - they used to be safe, but
- later they were changed to
unsafe. (Of course, such a change is a breaking change, but mistakes happen.)
- have been called as a part of (larger)
- Make our libraries and applications safer.
- Prevent accidental invocation of functions (3rd party, or even your own) that
However,
prudentcannot make/guarantee yourunsafecode to be "safe". No tool can fully do that (because of nature ofunsafe).- Using
prudentdoesn't mean you can ignore/skip reviewing/blindly trust any safe code that calls/interacts with/is used byunsafecode or with data used by both. "Unsafe Rust cannot trust Safe Rust without care." and "Unsafe code must trust some Safe code, but shouldn't trust generic Safe code."
prudent is no-std-compatible. It doesn't need allocation either.
All of prudent's "positive" functionality works on stable Rust (minimum version 1.39). However,
if you use assert_unsafe_methods feature to verify that unsafe_method is applied only to methods
that are indeed unsafe, that requires
nightly, and- access to have
#![feature(type_alias_impl_trait)]at the crate's top level (that is,src/lib.rs,src/main.rsor a binary crate's top level source file), and - access to set
RUSTFLAGS="-Znext-solver=globally". That is unfortunately not possible for doctests. See, and give thumbs up to, Related issues.
prudent is planned to be forward compatible. (If a need ever arises for big incompatibility, that
can go to a new crate.)
That allows prudent to be always below version 1.0. Then you can specify prudent as a
dependency with version 0.*, which will match ANY major versions (below 1.0, of course).
That will match the newest version (available for your Rust) automatically.
This is special only to 0.* - it is not possible to have a wildcard matching various major
versions 1.0 or higher.
Rust is a rich language and it allows complex statements/expressions. prudent tries to be
flexible, but it also needs to be manageable and testable. So, there may be code that prudent
doesn't accept (please do report it). Most likely if it involves advanced pattern matching
(destructuring).
prudent is to help you make unsafe code stand out more. Mixing unsafe with advanced pattern
matching or other complex syntax may sound exciting, but it makes reading the code difficult. Can
that be an opportunity for refactoring?
Several prudent macros duplicate their expression "parameter". In the generated Rust code the
parameter expression is evaluated only once, but it's present in the code twice - once in an
inactive if false {...} branch for verification, and once in the following active else {...}
branch.
That IS OK with macros by example (defined with macro_rules!), and OK with any well-behaving
procedural macros. However, if you pass in an expression that invokes a procedural macro that has
side effects or state, it's your problem. Such a macro contradicts Rust guidelines.
unsafe_fn validates that the function to be called is indeed unsafe. It does not use lints to
validate it, but it uses its own compile time checks instead. Those checks only work with functions
up to a certain number of arguments. Increasing the limit makes implementation longer (see
prudent::unlinted::expecting_unsafe_fn). For now, the limit is 12. To keep the overall API simple
enough, those checks can't be turned off.
Please subscribe for low frequency updates at peter-lyons-kehl/prudent#1.
Please contribute, or at least subscribe, and give thumbs up, to:
Sorted by importance (for prudent):
- rust-lang/rust#43031 rustdoc: allow full set of compiler options to be specified
- rust-lang/rust#29625
unboxed_closuresandfn_traitsfeature. - rust-lang/rust#88531 No way to get compile-time info from the type of local
- rust-lang/rust#63063 Tracking issue for RFC 2515, "Permit impl Trait in type aliases"
- rust-lang/rust#54726 Tracking issue for custom inner attributes
- rust-lang/rust#110613 Forbidding lints doesn't really work in macros
- rust-lang/rust#148942 cfg(test) is not set within the test code while compiling doctests
- rust-lang/rust#148183 rustdoc: Test & document test_harness code block attribute
- rust-lang/rust#56232 Oh rust doctest lints, where art þou? (Add a way to run clippy on doctests)
- rust-lang/rust#127893 doctest line number is
incorrect if used with
#![doc = include_str!()] - rust-lang/rustfmt#6047 Braces are removed from single-item import in macro where they are required
- rust-lang/rust#39412 declarative macros 2.0
- rust-lang/rust#65860 Re-land early syntax feature gating
- rust-lang/rust#15701 attributes on expressions
- rust-lang/rust#87022
--no-runflag inrustdoc - rust-lang/rust#143874
#![feature(const_trait_impl)] - rust-lang/rust#83527
${ignore(..._}metavariable/metafunction inmacro_rules!
- rust-lang/rust#148599 forward compatibility of
#![doc(test(attr(forbid(...))))]for lint groups - rust-lang/rust#149583 Doctests should not inherit features
- rust-lang/nomicon#506 note that a macro in a
#![forbid(unsafe_code)]library can emitunsafe - Veykril/tlborm#114 storing & (re)using variadic tuples
- dtolnay/trybuild#321 README: Avoid directory traversal
Thanks to Kevin Reid kpreid for a tip on Preamble for doc
tests....