Multiple mutable references to elements within one vector

You mean across threads I presume? Yes, that’s true. Is that a requirement for you?

It wouldn’t be safer though :slight_smile:. There would be warning signs for readers/users of code but that’s about it - compiler won’t help you anymore. Besides, if you wanted to modify a Particle across threads you’d need synchronization anyway.

What you could do to share the data immutably between threads is to create another struct, say ParticleView, that is a copy of the particle state you want to share, without the cells, and share that.

It means you cannot alias the same data mutably - ie there can be only one mutable reference to a given piece of data at once. split_at_mut is fine because it gives you disjoint locations of the slice - there’s no aliasing to the same location.

2 Likes

Ok, I see. It's not so I guess that's not a problem then. Thanks!

Oh, ok. But if the programmer can guarrantee that the indexes never are the same and that the vector isn't mutated during the borrows, then no aliasing to the same location should happen, thus it can't invoke undefined behavior? But this is not that important anymore, I think I have enough tools within safe rust now to handle this situation.

2 Likes

That's right - it's safe then. The problem is compiler cannot figure this out (i.e. the disjointness) on its own for things like slices. split_at_mut is implemented as unsafe code internally, but its implementation ensures that it doesn't hand out overlapping/aliasing mutable locations. You could do the same thing in your own code.

2 Likes

In my dream world, rustc would be clever enough to understand that for any slice a, a[i] and a[j] can be borrowed separately if i != j.

However, this would require the ability to prove that i != j at compilation time, which is difficult and sometimes not possible at all (e.g. if i and j are received from user input).

So I would totally understand if this capability only worked in the simplest cases, e.g. when i and j are consts. It is just a very useful capability to have in numerical code.

3 Likes

Thanks for clarifying this!

Yeah, I dream about this too! Proving i != j at compile time would indeed be very hard, so also having the option to tell the compiler that "this is not the same element, trust me" would be really nice. After NLL I hope this will be the next thing the compiler wizards will tackle :stuck_out_tongue:

From what I have heard of them recently, I have the impression that the next expressivity challenge that they have in mind is self-referential types, where you try to store together a value of a certain type and a reference to that value.

This is a notion which comes up all the time, but which the Rust type system is sadly unable to express cleanly today, as you can see by trying to define such a type:

struct SelfReferential {
    value: T,
    reference: &'/* ??? */ T,  // Will contain a reference to "value"
}

FYI, this is one of the roles of unsafe in Rust :slight_smile:

2 Likes

That is just super awesome! So many times I've wanted that!

I don't think it's so much about proving that i != j (although that's part of it) but more about the compiler doesn't intrinsically understand what data is being returned - a Vec (or more generally, some abstract data type) is just some data with methods, and indexing into it is just another function call. So even if it knows that i != j, it doesn't know that the index operation will actually return disjoint data. The only thing it understands at this level is structs and their fields - it knows that two fields of a struct are disjoint, and thus allows borrowing them mutably at the same time without any fuss.

It'd be awesome if the compiler could do this on abstract data types, but realistically, I don't know how one would achieve this.

4 Likes

At a conceptual level, I would say that different items of a slice and different fields of a struct are very similar. The key difference between them is that struct fields are always selected at compile-time, which makes it much easier to analyze disjoint borrows on them.

But of course, as you say, that would entail either giving slices some special treatment in the compiler, or increasing the language's expressivity until the notion of a disjoint borrow can be expressed in a method's interface.

2 Likes

I think the compiler already knows about slices: TySlice. So for them it's probably the case that proving i != j is all that's required, although I'm not certain.

But for arbitrary user types, yeah, it's a more difficult problem.

3 Likes

try this:

fn get_two_elements<T>(vec: &mut Vec<T>, first: usize, second: usize) -> (&mut T, &mut T) {
    assert!(first < second);
    assert!(second < vec.len());
    if let [first, .., second] = &mut vec[first..=second] {
        (first, second)
    } else {
        unreachable!()
    }
}

or

fn get_elements<'a, T>(vec: &'a mut Vec<T>, indexes: &[usize]) -> HashMap<usize, &'a mut T> {
    // indexes must be ordered
    let mut map = HashMap::new();
    let mut vec_ref = vec.as_mut_slice();
    let mut skipped_count = 0;
    for i in indexes {
        let start = *i - skipped_count;
        vec_ref = &mut vec_ref[start..];
        skipped_count += start;
        if let [first, rest @ ..] = vec_ref {
            map.insert(*i, first);
            vec_ref = rest;
            skipped_count += 1;
        } else {
            break;
        }
    }
    map
}

For reference, subslice patterns like [first, .., second] or [first, rest @ ..] were only stabilized in Rust 1.42.0, over 2 years after this thread, so they were not available at the time. The most idiomatic solution at the time would have been <[_]>::split_at_mut(), as suggested in this thread.

1 Like