rust-lang / rust-memory-model

Collecting examples and information to help design a memory model for Rust.
Apache License 2.0
126 stars 15 forks source link

Unsafe Mutability Polymorphism #31

Open arielb1 opened 8 years ago

arielb1 commented 8 years ago

One popular example of code where we are not certain about its UB status is mutability polymorphism:

struct BTree<K, V> { /* .. */ }

impl<K: Ord, V> BTree<K, V> {
    fn get<'a>(&'a self, key: &K) -> Option<&'a V> { /* ... */ }

    fn get_mut<'a>(&'a mut self, key: &K) -> Option<&'a mut V> {
        self.get(key).map(|v| unsafe { &mut *v })
    }

    // <or vice-versa>
}

Of course, the usage pattern is calling get_mut and then mutating the value. However, with both the ACA and CA models, a reference derived from an &-reference has no write permissions, and writing through it causes self-aliasing-violation and UB.

OTOH, having get call get_mut rather than vice-versa does not seem to create UB under both rules (because the reference is asserted only for reading), but without write-asserts to arguments we have the write-not-in-program issue.

Still, having one of these cases UB and the other well-defined is ugly, and hard to tell to users.

RalfJung commented 8 years ago

OTOH, having get call get_mut rather than vice-versa does not seem to create UB under both rules (because the reference is asserted only for reading), but without write-asserts to arguments we have the write-not-in-program issue.

Notice that this would be a violation of the types that are given; get_mut may assume that nobody else has any pointer to self (nor to anything owned or uniquely borrowed by self) and may hence optimize accordingly. I take it that's not captured by ACA/CA because get_mut does not actually mutate anything -- no assertion happens just by the mere presence of an argument of a given type (with the type implying some capabilities).

Could one argue that, when get is called with more capabilities than are needed (i.e., from get_mut), those additional capabilities flow through the function and the returned pointer actually carries the capability to mutate, and hence is safe to cast to a mutable borrow?