I had this in my head for quite some time and would like to hear some feedback to it. My proposal can easily be combined with this one: "Split keyword unsafe into unsafe / checked"
In the 2024 edition, it seems that a linter change will be included:
unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T> {
PhysicalMapping::new(
physical_address,
self.as_ptr(physical_address),
size,
size,
*self,
)
}
Now emits this warning:
warning: call to unsafe function `acpi_rs::PhysicalMapping::<H, T>::new` is unsafe and requires unsafe block (error E0133)
--> rose-kernel/src/acpi/acpi_mapping_handler.rs:30:9
|
30 | / PhysicalMapping::new(
31 | | physical_address,
32 | | self.as_ptr(physical_address),
33 | | size,
34 | | size,
35 | | *self,
36 | | )
| |_________^ call to unsafe function
|
= note: for more information, see issue #71668 <https://github.com/rust-lang/rust/issues/71668>
= note: consult the function's documentation for information on how to avoid undefined behavior
note: an unsafe function restricts its caller, but its body is safe by default
--> rose-kernel/src/acpi/acpi_mapping_handler.rs:29:5
|
29 | unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `#[warn(unsafe_op_in_unsafe_fn)]` on by default
I personally like this change, since it prevents oversight of using unsafe functions, but it also has a downside: It introduces more nesting and lines.
Another thing thats is slightly annoying sometimes is this syntax:
let Some(x) = (unsafe { get_x() }) else {
return;
}
The braces around the unsafe block are redundant to the reader, but necessary for it to compile, because else doesn't like being put after curly braces when not used as an if / else.
My proposal would be, to add another way to use unsafe:
let Some(x) = unsafe get_x() else {
return;
}
In this case, unsafe is not a block, but a modifier, allowing the following expression or statement to be unsafe. Another example with a statement:
unsafe for page in list_pages_unsafe() {
map_page_unsafe(page);
}
In this case, the entirety of the for statement, both the iterator expression and body would allow for unsafe things to happen.
The only purpose of this change is to avoid pointless nesting blocks for one liners, but especially in low level code, where unsafe is used frequently, this would be a huge improvement. Alternatively, one could just write a simple macro in the form of:
let Some(x) = do_unsafe!(get_x()) else {
return;
}
But for such a simple thing, I believe this is a needless abstraction, and might lead to stupid human errors like:
- Tom realized that the logger is always initialized when the do_unsafe macro is called, and puts some logging for reasons only he is smart enough to grasp.
- This makes it into production because Rick reviewing it doesn't see an issue with it. It works after all.
- A couple weeks later, an unsafe step is added, that runs parallel to the logger initialization. It's an edge case so it doesn't break most of the time.
- Under the read world workload, the logger takes a lot longer to initialize. It now breaks in production but is rarely reproducible in testing.
- You got yourself a 80 hour workweek in order to fix it.
Sure it's a dumb idea to add side effects to random stuff but in reality we all are stupid sometimes. So I'd like to avoid using a macro.