Crate prudent

Crate prudent 

Source
Expand description

To use the crate without lints:

  1. Invoke this macro at the top of your crate (lib.rs, or in your binary crates if they don’t have lib.rs). Like this, with the leading double colon ::
    ::prudent::load!();
    But, from here on, never refer to ::prudent. Instead, use crate::prudent. (You could use self::prudent at the top level of your lib.rs (or in the top level of your binary crates), but crate::prudent works everywhere.)
  2. Wildcard import. This must be without any leading double colon ::!
    use crate::prudent::*;

If you need lints

  • in doctests or custom integration tests (even if your crate is published on <crates.io>); or
  • if your crate is not published on <crates.io>

then pass the first parameter, a relative/absolute file path to your local clone/git submodule copy/other copy of src/linted.rs. So, instead of #1 above, have something like:

   ::prudent::load!("../../prudent/src/linted.rs");
   use crate::prudent::*;

Pass a second parameter, after ->, if you want the load in a module with name of your choice (other than prudent). GitHub Actions results

§Summary

prudent helps you minimize the amount of Rust code that is marked as unsafe.

  • ergonomic (as much as possible)
  • obvious
  • lightweight (no dependencies, no procedural macros - fast build)
  • zero-cost (for binary size, speed and memory), verified in compile time

§const-friendly

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

§Lints, loading and import

§Lints and loading

Because of some Rust annoyances (more below), a part of this crate needs to be “loaded”. (That is not at runtime/dynamic, but it’s done at compile time.) You do it only once per your crate (usually in src/lib.rs):

  • If you want to apply lints to the macro-generated code (which is highly recommended), your crate needs to contain/have access to (a copy of) prudent’s file src/linted.rs, which you “load” with ::prudent::load!(...).
  • If you don’t need lints, just ::prudent::load().

Both ways of ::prudent::load!(...) create a module, called prudent by default. If your crate already uses prudent identifier, you can choose a different identifier for prudent’s top-level module (by passing an optional “parameter” to ::prudent::load!(...)).

§Import

Have a wildcard import use crate::prudent::*. Do not import just a specific “top level” (client code-facing) macro(s) that you invoke. That is regardless of whether you apply the lints (where your include src/linted.rs), or not.

(At the top level of your crate you could use self::prudent::* instead, but that will not work in modules. However, use crate::prudent::* works everywhere).

§Annoyances

§Rust annoyances

prudent is badly affected by lack of lint control in macros: rust-lang/rust#110613 - please give it thumbs up. The pains (that pend rust-lang/rust#110613):

  • prudent’s documentation on docs.rs shows code examples first, and only then documentation text (prose).

  • You need a wildcard import use crate::prudent::* - not just import a specific “top level” (client code-facing) macro(s) that you invoke.

    It’s not enough to import just specific macros that you invoke (because the internal “linted” macros are loaded in your crate’s namespace, and hence they can’t use $crate metavariable to refer to the rest of the macros and non-macro functionality).

  • In doctests

    • load with any: like ::prudent::load!(any: "linted.rs");
    • import with use crate::prudent::*; which you put outside of your fn main()
    • have fn main()
      • do not have the test logic at the top level, otherwise rustdoc/doctest mechanism automatically puts the whole doctest code inside fn main(), which will include ::prudent::load!(...) and use crate::prudent::*, which will fail with very strange errors. Even if all you are testing is const, have an empty fn main() {}. (If you run cargo clippy and it complains, see prudent’s source code on how to allow clippy::needless_doctest_main.)

§Limitation of lint control for unsafe_method

Macro unsafe_method (normally accessed as crate::prudent::unsafe_method)

TODO TODO TODO!

§Quality assurance

Checks and tests are run by GitHub Actions. See results. All tests run on Alpine Linux (without libc, in a rust:1.87-alpine container):

  • rustup component add clippy rustfmt
  • cargo clippy
  • cargo fmt --check
  • cargo doc --no-deps --quiet
  • cargo test
  • cargo test --release
  • with MIRI
    • rustup install nightly --profile minimal
    • rustup +nightly component add miri
    • cargo +nightly miri test

§Verification of expected errors

§API and examples

Following are all the positive examples. They are also run by the above GitHub Actions as doctests.

For negative examples, which catch unintended unsafe functions/expressions/access, see documentation of each prudent macro.

§unsafe_fn

::prudent::load!(any: "linted.rs");
use crate::prudent::*;

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

§unsafe_method

§unsafe_method > self: shared reference

::prudent::load!(any: "linted.rs");
mod module {
  use crate::prudent::*;
  // Works for Copy types
  const _: u8 = unsafe_method!(1u8 =>@ unchecked_add => 0);
  //const _: u8 = unsafe_method!(({#[forbid(unused)] let v = 1u8; v}), unchecked_add, 0);
  //const _: u8 = unsafe_method!(#[allow_unsafe] 1u8, unchecked_add, 0);
  //const _: u8 = unsafe_method!(#[expect_unsafex] 1u8, unchecked_add, 0);

  //const _: u8 = unsafe_method!(({#forbid(unused) let v = 1u8; v}), unchecked_add, 0);
  const _: u8 = unsafe_method!(~allow_unsafe 1u8 =>@ unchecked_add => 0);
  //const _: u8 = unsafe_method!(~expect_unsafe 1u8, unchecked_add, 0);
}
fn main() {}
let _todo = ();
//# use prudent::unsafe_method;
//const _: u8 = unsafe_method!(~expect_unsafe ~allow_unsafe 1u8, unchecked_add, 0);
let _todo = ();
//# use prudent::unsafe_method;
//const _: u8 = unsafe_method!(~allow_unsafe ~expect_unsafe 1u8, unchecked_add, 0);
::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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 {};
    // Works for non-Copy types
    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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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);
}

§unsafe_mut - two args, lifetimed dyn reference

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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);
}

§unsafe_mut - two args, lifetimed slice

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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);
}

§unsafe_mut - two args, typed basic reference

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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]);
}

§unsafe_mut - two args, typed dyn reference

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
fn main() {
    let mut b: bool = true;
    let pt: *mut bool = &mut b as *mut bool;

    unsafe_set!(pt, false);
    unsafe_set!(pt, true);
}
::prudent::load!(any: "linted.rs");
use crate::prudent::*;
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

::prudent::load!(any: "linted.rs");
use crate::prudent::*;
static mut B: bool = true;

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

§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.

§Always forward compatible

prudent is planned to be always below version 1.0. So it will be forward compatible. (If a need ever arises for big incompatibility, that can go to a new crate.)

That allows you to 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 with prudent macros

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.

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 to refactor?

§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.

§Updates

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

§Side fruit

Modules§

linted
Linted macros.
unlinted
“Unlinted” functionality (anything else than linted macros)

Macros§

internal_prudent_unsafe_method
Invoke an unsafe method. For methods that have a receiver parameter (&self, &mut self, self). For associated functions (implemented for a type but with no receiver) use unsafe_fn, and pass the qualified name of the associated function to it.
internal_prudent_unsafe_mut
Deref a mut pointer and yield a mut reference.
internal_prudent_unsafe_ref
Deref a pointer (either const or mut) and yield a read-only reference.
internal_prudent_unsafe_set
Assign the given value to the location given in the pointer.
internal_prudent_unsafe_static_set
Set a value of a static mut variable or its (sub…-)field, but isolate unsafe {...} only to that assignment.
internal_prudent_unsafe_val
Get a (copy of) value from where the pointer points. For core::marker::Copy types only.
load
Invoke from the top level of your crate, and only once (once per crate).