Except that I don't care whether the compiler can see the non-aliasing. I only care that the user writing the unsafe code can prove that there won't be conflicting memory actions. Although indexing is a function call, it is a completely predictable function call that unsafe code writers understand completely, and so it isn't a burden to ask people to understand that it doesn't involve actually reading from the slice being sliced.
However, I think the "Tootsie Pop" proposal will only make this situation worse - by making it safer to change safe code in a module with unsafe invariants, the proposal only encourages this misunderstanding without really mitigating its effects. This proposal lulls people into a false sense of security.
Unfortunately, I believe that concurrent code does assume this already - there are many cases where AtomicUsize
is used instead of AtomicPtr
, including the fact that pointers need to be aligned, so when tagging pointers people indirect through usize
.
I think this is assuming the main point of contention here, whether the boundary of unsafety is a module. I would argue that even when you use unsafe code, Rust can still prove that pointers don't alias, because you are responsible for leaving pointers nonaliasing when you exit an unsafe block (where I consider the boundary to be). If, in contrast, you assume the boundary to be the module, then what you said is correct, but only with that highly contentious assumption.
Except that no type system is ever sufficient. The beauty of privacy and unsafety is that you can make arbitrarily complex invariants that will never be included into the language, where you internally prove that your code is correct and expose the correct interface to ensure that. Arguing that we shouldn't pay attention to those uses of unsafety because the language will eventually patch the deficiencies is wrong because there are an infinite number of deficiencies.
Except that this is exactly the point being argued. If the unsafe boundary ends at the end of an unsafe block (which I think it should), then the compiler can assume that the assembly code does not stash the pointer somewhere, because otherwise the unsafe block would extend until that problematic behavior ended.
I think that this should be the behavior of plain unsafe
blocks. If there is a need for aliasing unsafe
blocks (which I don't think there is, given the right definition of alias), then those should be the specially distinguished ones, since those cause widespread, extra unsafe unsafety.
I like your distinction between the three zones. I would argue that 1 and 3 should be bounded by unsafe
blocks (with 3 possibly even smaller) and 2 should not be constrained, stretching possibly to the whole crate, though authors should be encouraged to keep it much smaller.
Essentially, library authors are required to make sure that no code will break the type system/language invariants. Since they cannot know what usage code will be, zone 2 has to be bounded by the crate boundary. However, they are free to let unsafety bleed into however much or however little of their library as they wish, as long as the fastidiously avoid doing anything problematic.
As an example, in the implementation of BTreeMap
, the node
module uses lots of unsafety for speed. Various functions have preconditions that, if violated, cause memory safety. These are checked by debug_assert
s, meaning that in a debug build, zone 2 is contained within the node
module, but in a release build, it technically reaches into the main BTree map
module. This seems very reasonable to me - functions are called with their preconditions satisfied, and, to ease development, this is double checked. However, the region in which someone could do something bad on a release build is larger to cut down on overhead, even larger than a single module.
The issue is that the BorrowRef
is being dropped, and that doesn't touch the acual data stored in the RefCell
. It does act somewhat barrier-like because it writes to the borrow tracking state, but that memory is distinct from the read in question, of the actual data stores beside the borrow tracking state.