Skip to content

prudent-rs/prudent

GitHub Actions results

Summary

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

API and examples

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.

unsafe_fn

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);

unsafe_method

self is Copy, by value

use prudent::unsafe_method;
const _: u8 = unsafe_method!( 1u8 =>. unchecked_add => 0 );

self is not Copy, by shared reference

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);
}

unsafe_method > self: mutable reference

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);
}

unsafe_method > self: by value

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;
    }
}

unsafe_ref

unsafe_ref - one arg, basic reference

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);
}

unsafe_ref - one arg, slice

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);
}

unsafe_ref - one arg, dyn reference

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() {}

unsafe_ref - two args, lifetimed reference

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);
}

unsafe_ref - two args, lifetimed dyn reference

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() {}

unsafe_ref - two args, lifetimed slice

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);
}

unsafe_ref - two args, typed basic reference

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);
}

unsafe_ref - two args, typed slice

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]);
}

unsafe_ref - two args, typed dyn reference

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);
}

unsafe_mut

unsafe_mut - one arg, basic reference

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);
}

unsafe_mut - one arg, slice

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);
}

unsafe_mut - one arg, dyn reference

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);
}

unsafe_mut - two args, lifetimed reference

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) };
}

unsafe_mut - two args, lifetimed dyn reference

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) };
}

unsafe_mut - two args, lifetimed slice

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) };
}

unsafe_mut - two args, typed basic reference

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);
}

unsafe_mut - two args, typed slice

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) };
}

unsafe_mut - two args, typed dyn reference

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);
}

unsafe_val

Only for types that implement/derive core::marker::Copy.

unsafe_val - one arg, basic

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);
}

unsafe_val - two args, typed

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);
}

unsafe_set

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);
}

unsafe_static_set

use prudent::unsafe_static_set;
static mut B: bool = true;

fn main() {
    unsafe_static_set!(B, false);
}

const-friendly

Results of prudent's macro invocations are const (if the original invocation/expression would also be const).

Quality assurance

Continuous integration

GitHub Actions runs on Alpine Linux (without libc). See the reports.

  • cargo test
  • cargo clippy for linting
  • MIRI

Verification of expected errors

unsafe_method

TODO

Details

prudent helps both authors, reviewers and all of us:

  • Authors/maintainers:
    • Notice/prevent accidental (unintended), or unnecessary, unsafe code:
      • function/method calls:
        • in parameters to calls to an unsafe function 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 an unsafe method
      • variable access:
        • TODO: static mut variables
        • TODO: fields of union types
      • value cast (to a different type):
        • TODO: in expressions whose deref is unsafe
  • 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
      1. have been called as a part of (larger) unsafe {...} block, and
      2. they used to be safe, but
      3. later they were changed to unsafe. (Of course, such a change is a breaking change, but mistakes happen.)
    • Make our libraries and applications safer.

However,

Compatibility

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.rs or 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.

Always forward compatible

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.

Scope

Not supported: Pattern matching

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?

Not supported: Procedural macros with side effects

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 limit max 12 arguments

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.

Updates

Please subscribe for low frequency updates at peter-lyons-kehl/prudent#1.

Side fruit, related issues, credits

Please contribute, or at least subscribe, and give thumbs up, to:

Related issues

Sorted by importance (for prudent):

Side fruit

Credits

Thanks to Kevin Reid kpreid for a tip on Preamble for doc tests....

About

Make Rust code safer

Resources

License

Apache-2.0 and 2 other licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
BSD-2-Clause
LICENSE-BSD
MIT
LICENSE-MIT

Contributing

Stars

Watchers

Forks

Packages

No packages published

Languages