Unsafe categories

I personally feel like it would be nicer to just add the ability to use the unsafe keyword in a smaller scope than a whole block, namely, on each individual unsafe operation.

In terms of syntax, a possibility for each unsafe feature, i.e. each of

The following language level features cannot be used in the safe subset of Rust:

  • Dereferencing a raw pointer.
  • Reading or writing a mutable or external static variable.
  • Accessing a field of a union , other than to assign to it.
  • Calling an unsafe function (including an intrinsic or foreign function).
  • Implementing an unsafe trait.

could be

  • Dereferencing a raw pointer
    let x: *mut u8 = …;
    let old_value = unsafe *x;
    unsafe *x = new_value;
    
  • Reading/writing a mutable/external static
    unsafe FOO += 1;
    let foo = unsafe FOO.clone();
    
  • Accessing a field of a union
    let some_union = …;
    let f = some_union.unsafe field;
    
  • Calling an unsafe function or method
    let x = unsafe f(42);
    let y = foo.unsafe bar(baz);
    
  • Implementing an unsafe trait already has fine-grained unsafe annotations. You cannot suddenly dereference an unsafe pointer in an unsafe impl Trait for Foo {} without using unsafe another time.

Note for clarification: In the proposed syntax above, the unsafe always only allows the single operation it applies to. For parsing, consider unsafe to bind to a single succeeding token-tree, nothing more: Here’s some pseudo-code using parentheses to clarify precedence: (not valid Rust code under the proposal!)

let x: *mut u8 = …;
let old_value = (unsafe *)x;
(unsafe *)x = new_value;

unsafe FOO += 1;
let foo = (unsafe FOO).clone();

let some_union = …;
let f = some_union.(unsafe field);

let x = unsafe f(42);
let y = foo.(unsafe bar)(baz);

In a chain of unsafe methods, called on a mutable static, returning a pointer which is dereferenced, could thus write

let x = unsafe *unsafe FOO.unsafe bar().unsafe baz(unsafe qux());
// FOO is a mutable static
// the * dereferences a raw pointer
// bar, baz are unsafe methods
// qux is an unsafe fn

instead of

let x = unsafe { *FOO.bar().baz(qux()) };

If foo and bar are unsafe fn, then unsafe foo(bar()) won’t work, only unsafe foo(unsafe bar()) would. With this approach, there’s no accidental or non-obvious additional usage of unsafe language features like it’s possible in an unsafe {} block. I.e. a reader of unsafe { foo(bar()) } not immediately notice that there’s two unsafe functions being called here.


This feature would – of course – come with an opt-in/allow-by-default lint to disallow unsafe {} blocks (and I guess, that lint would/should also imply unsafe_op_in_unsafe_fn).

7 Likes