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 usingunsafe
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
).