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

Using `*mut` with an active `&mut` to a parent container #1

Closed aidanhs closed 2 years ago

aidanhs commented 8 years ago

Probably covered by the 'key question' in https://github.com/nikomatsakis/rust-memory-model/blob/master/litmus_tests/overlapping_ref_mut_star_mut.md, but this is something I've encountered recently so thought I'd raise it.

fn x() -> Vec<usize> {
    let mut x = vec![1, 2, 3, 4, 5, 6];
    let mut p = ptr::null_mut();
    for v in x.iter_mut() {
        if *v % 2 == 0 && p != ptr::null_mut() {
            unsafe { *p = *v }
        }
        p = v as *mut _;
    }
    x
}

Is the compiler allowed to return vec![1, 2, 3, 4, 5, 6] (e.g. generating a new vector after the loop and pointer writes) because it cannot see any change occurring through the mutable reference to the overall vec? Or is this something you could get away with because you're mutating the previous elements of the vec (without a direct mutable reference to them)?

(in the code that I'm looking at, this is split across 3 different functions for 1) the traversal over a custom tree-like structure, 2) storing pointers into the structure and 3) mutation of pointers based on a condition. So using a function as the maximum boundary of unsafety wouldn't be sufficient to support it)

aidanhs commented 8 years ago

Not really related, but since this issue is open: is there a litmus test for converting *mut to &mut (i.e. reverse of https://github.com/nikomatsakis/rust-memory-model/blob/master/litmus_tests/ffi_ref_mut_to_star_mut.md) and what invariants that implies?

For example, someone might have multiple aliasing *mut pointers that they cast to &mut for the duration of a function for convenience of calling methods on them. The correct solution is probably to make the whole function unsafe, but in practice I believe that this has always been permitted by rust because noalias was only ever added to function parameters - is there a chance rust may want noalias annotations on variables, which could prohibit this casting?

aidanhs commented 8 years ago

A thought on my original case: rust needs to permit things like

fn y() {
    let mut x = vec![1usize, 2, 3];
    let p = &mut x[1] as *mut _;
    unsafe { ffi(p) };
}
unsafe fn ffi(p: *mut usize) {
    *p.offset(-1) = 3;
    *p.offset(0) = 2;
    *p.offset(1) = 1;
}

for ffi. So creating a pointer from a mutable borrow of a vec should probably give access to the whole vec, which means my initial example should be fine.

The next question for me is how far 'upwards' the permissible access goes. If there are four functions on the stack, each having done a split_at_mut on a slice and passed part of it down, when the final function calls into an ffi function with a pointer to a single element of the slice can the compiler assume anything about which elements of the whole slice are accessed?

arielb1 commented 8 years ago

Well, it works with a safe pointer:

fn x() -> Vec<usize> {
    let mut x = vec![1, 2, 3, 4, 5, 6];
    { // extra block only needed because of lexical lifetimes
    let mut p = &mut 0;
    for v in x.iter_mut() {
        if *v % 2 == 0 {
            *p = *v;
        }
        p = v;
    }
    }
    x
}

fn main() { println!("{:?}", x()); }
arielb1 commented 8 years ago

The next question for me is how far 'upwards' the permissible access goes. If there are four functions on the stack, each having done a split_at_mut on a slice and passed part of it down, when the final function calls into an ffi function with a pointer to a single element of the slice can the compiler assume anything about which elements of the whole slice are accessed?

That's where the "tootsie pop model" comes into play - if you are passing raw pointers around, we can't second-guess you, but if you wrap a safe interface around it, we will.

nikomatsakis commented 8 years ago

This same pattern arose at various points in btree iterators, iirc. It may still.

aidanhs commented 8 years ago

That's where the "tootsie pop model" comes into play - if you are passing raw pointers around, we can't second-guess you, but if you wrap a safe interface around it, we will.

I admit I've warmed to it since first reading about it, and I think I'd be happy with it subject to having sufficient knobs to twiddle to carefully opt into rustc assumptions. I know this has been mentioned before.

RalfJung commented 2 years ago

Closing as being (preliminarily) answered by Stacked Borrows. The code given above is fine, in particular since basically the same can be implemented without raw pointers.