Is it possible to define various level for safety?

A real-world app may have more than one unsafe level:

level -1: unsafe operations, which may cause double free and various hardware related bug.

level 0: safe operations that access user's password

another level 0: safe operations that send messages to a private LLM api (may waste a lot of money)

level 1: publish comment into forums which could be redraw easily.

Rust could alert users very well if they misuse some unsafe code, but IMHO, use pure safe rust code to launch a nuclear missile is more dangerous than many unsafe operations.

Is it possible to utilize the unsafe block to define more than just one unsafe sematics?

unsafe fn transmute() {}//transmute is always unsafe
unsafe(abort) fn panic() {
    panic!("suppose you are calculating for 10 hours, and the program aborts")
}
unsafe(any_custom_ident) fn launch_nuclear_weapon() {
    // since we cannot cover all situations, define such a keyword is fine
}

Any function which call any type of unsafe function should either:

  1. use an unsafe block without any modifier.
  2. use an unsafe block with the correct modifier, and put that modifier intofunction's signature.
fn trust_me() {
 unsafe{/*I can call any function here, I said it is safe since.......*/}
}
unsafe (ptr) fn play_with_ptr() {
    unsafe (ptr) {
        play_with_ptr();
    }
}

rule 2 is a coloring rule, each function calling unsafe X must be X. To avoid this, a pure unsafe block is used.

First of all, keep in mind that Rust is not a sandbox language. It fundamentally trusts the programmer and will allow the source code to turn off or bypass all safety restrictions. You can't use Rust's unsafe to protect systems from malicious or very badly broken Rust code. The safe/unsafe split is only a helpful tool for cooperative programmers.

Not-unsafe code is still allowed to do lots of dangerous, stupid, and destructive things. Non-unsafe code is allowed to delete data, expose sensitive information, be incompetent at controlling system access, and connect missile control to AI agents.

Currently unsafe is only focused on specifics of memory safety and integrity of the program itself (avoiding UB), which can have precisely defined boundaries. This works because the requirements are limited in scope, defined by the language, and pretty universal across all programs and purposes.

Adding protections for other categories of badness would require defining exactly what is allowed and where, how it affects data moving through the program, generic code that manipulates it, dynamically invoked code, etc. Assuming you want these features to actually do something and catch bugs in non-trivial cases, implementing them is challenging. For something like protecting a password you need to define what data is the password, track it as it passes through the program, and teach the compiler where it can safely end up and where it can't. In language design such features are known as "taint tracking" and "effect systems". It gets pretty complicated - .map() is generic, so it must be safe to call when it processes non-password, but not allowed to turn unsafe-to-handle password into safe-to-handle bytes just because you invert case of password's letters or something like that. You may have a web form that takes a password, and want to ensure that password-ness of the data is carried through your web framework, deserializer, handlers, and only make password handling require unsafe and not taint the whole web server and not lose the taint bit on the first dyn Future.

7 Likes

Basically no. To a language, saving your password in a password manager looks exactly the same as leaking it to a pastebin.

The reason unsafe is such a critical difference is that UB means you can't trust what you see. If you get it wrong and hit UB, you might have logging that "shouldn't" have happened, might be missing logging that "should" have happened, might be running code under conditions that you "checked" etc.

So long as you avoid UB, you can have telemetry, you can do code analysis, etc.

4 Likes

I have such proposal "safety tags" for that: RFC: Safety Tags by zjp-CN · Pull Request #3842 · rust-lang/rfcs · GitHub and slides.

Your example will be written as follows

-unsafe(reason) fn launch_nuclear_weapon() { ... }
+#[safety::requires(reason)] unsafe fn launch_nuclear_weapon() { ... }

fn foo() {
+   #[safety::checked(reason)]
    unsafe { launch_nuclear_weapon() }
}
2 Likes

I guess if Rust were to allow this kind of functionality, it would need a dependently typed system. As of now, we don't have such a feature to enforce user-defined safety invariances, but there are several verification crates to eliminate logical errors at compile-time.