There are multiple things to pay attention to, here:
-
Should a macro using
unsafe
becomeunsafe
itself? I don’t think this is desirable. See::std::await!
or::pin-utils::pin_mut!
: safe constructions using underlyingunsafe
. It is the same as a non-unsafe
function wrappingunsafe
behavior in a (hopefully) safe manner, thus not needing to beunsafe
itself.On the other hand, if a macro expansion cannot be proven to be always sound, then the macro has to be defined without
unsafe
in it, thus forcing the caller to explicitly use anunsafe
scope. Sadly, the “unsafen
ess” of the macro would not be visible at the “header level”; it could just be documented (and maybe also with a naming convention, likeunsafe_foo!
), which means that the error message raised might not be great. -
unsafe hygiene, i.e., an input argument
$macro_arg:expr
should not be allowed to “unsafe
-ly evaluate”$macro_arg
(e.g. feeding*::std::ptr::null()
as$macro_arg:expr
should raise an error since it requires "unsafe
evaluation", but feedingunsafe { *::std::ptr::null() }
as$macro_arg:expr
should be allowed (unsafe
being the keyword making all the evaluations in its scope be automagically taggedsafe
, hence its unsafety)). I’m not being very rigurous but you get the idea.In the meantime, the macro maker needs to be careful to never “evaluate an expression argument” (that is, use a
$macro_arg:expr
) inside anunsafe
block, using the following pattern:
unsafe fn get_foo () {}
macro_rules! eq_to_foo {(
$macro_arg:expr
) => (
match $macro_arg { macro_arg => unsafe {
// Look, no metavariables within the unsafe block !
macro_arg == get_foo()
}}
)}
// Note that for this very particular case, we could more simply write:
macro_rules! eq_to_foo {(
$macro_arg:expr
) => (
$macro_arg == unsafe { get_foo() }
)}
-
unsafe
detection: there are many talks about, as a library user, being able to detect / warn and even forbidunsafe
usages. The problem is that current “solutions” do not detectunsafe
usage through macro invocations. For instance:-
::cargo-geiger
, a tool to walk through the dependency graph and count the number ofunsafe
usages encountered in each crate (imho counting is not the best metric, but that is out of topic here):- Unsafe code inside macros are not detected. Needs macro expansion
- Using
#![forbid(unsafe_code)]
(orRUSTFLAGS=-Funsafe_code
):- It detects if the macro was defined within the same crate. But a macro from a different crate, even when in the same workspace, will sneak its way past the
forbid(unsafe_code)
restriction (See here, look at the difference between 1m48 and the end).
- It detects if the macro was defined within the same crate. But a macro from a different crate, even when in the same workspace, will sneak its way past the
-