Featured image of post forbid means forbid, until linkme needs a word

forbid means forbid, until linkme needs a word

There’s a line at the top of every production crate in rust-tool-base that I’m quietly proud of: #![forbid(unsafe_code)]. And there are a couple of files that have to say #![allow(unsafe_code)] instead. Not because I wrote anything unsafe. Because a macro did, on my behalf, and forbid doesn’t care whose unsafe it is.

Why forbid, and why it isn’t the whole story

rust-tool-base makes a bold promise: no unsafe in its own code. The strong form of that is forbid, not deny. deny(unsafe_code) makes unsafe a compile error that any module can quietly re-permit with its own #[allow]. forbid can’t be overridden from inside the crate at all. That’s the appeal: nobody gets to wave unsafe through in a hurry.

So the workspace lint sits at deny, and every production lib.rs then tightens it to forbid:

# Cargo.toml
[workspace.lints.rust]
unsafe_code = "deny"
// crates/rtb-error/src/lib.rs
#![forbid(unsafe_code)]

Why deny at the workspace but forbid in each crate? Because deny leaves an escape hatch open for the rare file that genuinely needs one, while forbid slams it shut everywhere it can. Almost every file gets forbid. A tiny number need the hatch.

The files that need the hatch

The command and provider registries use linkme’s distributed_slice so backends can register themselves at link time, without life before main. And the linkme attribute expands to code carrying a #[link_section], which the unsafe_code lint counts as unsafe. So any file using the attribute, whether it declares a slice or registers into one, can’t live under forbid.

Here’s the Gitea release backend doing exactly that, from crates/rtb-vcs/src/gitea.rs:

#![allow(unsafe_code)]
// ...
/// Link-time registration entry.
#[distributed_slice(RELEASE_PROVIDERS)]
fn __register_gitea() -> Box<dyn ProviderRegistration> {
    Box::new(RegisteredProvider { source_type: "gitea", factory: factory as ProviderFactory })
}

That #![allow(unsafe_code)] isn’t there because the backend does anything dangerous. It’s there because the registration macro emits a #[link_section], and forbid would, correctly by its own rules, refuse to compile the file.

Where that leaves the promise

The guarantee survives, with an exception you can point at. Every production crate forbids unsafe outright. The workspace sits one notch looser at deny, precisely so the handful of files that use linkme (and a couple of test files that need Rust 2024’s unsafe env mutation) can open a narrow, module-scoped #![allow(unsafe_code)] with a written reason. The absolutist rule met a macro that writes a link_section for you. The answer wasn’t to drop the rule, it was to keep forbid everywhere it can hold and clearly label the one or two spots where it can’t.

Built with Hugo
Theme Stack designed by Jimmy