Comparing vector to one of its own slices?


#1

Can’t seem to find the wiggle room to compare v to a reference of its own slices

    let v: Vec<u32> = vec![1, 2, 3, 4];
    let v2: Vec<&[u32]> = v.chunks_mut(2).collect::<Vec<&[u32]>>();
    
    let mut c = 0;
    v2.iter().map(|x| {
        let f = x.eq(v[c..x.len()]);
        assert!(f);
        
        c += x.len();
    }).for_each(drop);

I’m honestly stumped. I guess I could clone v, but that seems off just for just a comparison


#2

edit I haven’t noticed you’re using chunks_mut rather than immutable chunks. Mutable aliasing is a no-go.

The trick is to compare slices with slices. Most methods on vectors are actually slice methods and rely on vectors dereferencing to a slice. Try &v[range] (with & to get a slice instead of the odd [] type representing variable number of bytes).

You don’t need eq; == works fine.


#3

I actually made a mistake and used chunks() instead of chunks_mut() in the example. I think I’ve worked out the reference type issues. But I am having problems working out the mutability issue. Namely

cannot borrow ‘v’ as immutable because it is also borrowed as mutable

mutable borrow occurs here
let v2: Vec<&[u32]> = v.chunks_mut(2).collect::<Vec<&[u32]>>();

immutable borrow occurs here
v2.iter().map(|x| {

borrow occurs due to the use of ‘v’ in closure
*x == v[c..x.len()];

I don’t think its possible? Seems like a common use case. Make a vector. Slice it mutably. Compare parts of original vector to new slices. Here’s an updated example

    let v: Vec<u32> = vec![1, 2, 3, 4];
    let v2: Vec<&[u32]> = v.chunks_mut(2).collect::<Vec<&[u32]>>();
    
    let mut c = 0;
    v2.iter().map(|x| {
        *x == v[c..x.len()];
        
        c += x.len();
    }).for_each(drop);

#4

This example (with [c..x.len()] fixed as [c..c+x.len()]) seems to just assert that chunks_mut works properly? What is your real use case? In some cases you probably won’t get around working with immutable slices, indices only, or cloning.

BTW, map(|x| ...).for_each(drop) seems to be the same as for_each(|x| ...)?


#5

I’m writing a test and making sure a certain implementation using chunks_mut produces the correct results. [c..x.len()] instead of [c..c+x.len()] is indeed a bug, one I haven’t got around to looking at because I’m stuck trying to compare a vector to one of its own mutable slices, which I guess is impossible?


#6

You can’t borrow a Vec mutably and then compare the mutable slices against the Vec itself. As far as the compiler is concerned, there’s no guarantee that you won’t attempt to mutate through the mutable slice and potentially cause (illegal) aliasing with your immutable view of the Vec.

Is there a reason you can’t use chunks instead of chunks_mut for the test? I realize it’s not the same as your real code but unless you’re testing chunks vs chunks_mut impl itself (which you’re not), it shouldn’t matter.

Alternatively, just clone the Vec.


#7

Yeah, just needed to confirm that this is actually impossible. I understand Rust doesn’t allow borrowing an already borrowed mutable reference and for good reason, but is there an unsafe escape hatch for this?

Right now I’m doing

    let v: Vec<u32> = vec![1, 2, 3, 4];
    let v_to_cmp = v.clone();
    let v2: Vec<&[u32]> = v.chunks_mut(2).collect::<Vec<&[u32]>>();
    
    let mut c = 0;
    v2.iter().for_each(|x| {
        *x == v_to_cmp[c..c+x.len()];
        
        c += x.len();
    });

But just feels so wrong. I want to tell the compiler, this time, you can trust me.


#8

unsafe is the typical escape hatch. In this case, you can do something like:

let v2: Vec<&[u32]> = v
        .chunks_mut(2)
        .map(|x| unsafe { std::mem::transmute(&*x) })
        .collect::<Vec<&[u32]>>();

The transmute to a shared (immutable) slice causes the compiler to not propagate the mutable borrow of v that chunks_mut took.

The above is actually safe since you don’t mutate anything in the Vec and neither does ChunksMut.


#9

Yikes! Let’s please not encourage use of transmute without explicit type annotations. (personally I can’t even tell what you’re transmuting here!)

Really, what you have done is not unreasonable, unless you later find that there are critical performance issues.

And if you do encounter such an issue, come up with the smallest unit of missing functionality from slices that is “safe but requires unsafe,” and write a helper function around it. (and if enough people need it, it may one day go in the stdlib)

(I’m not sure what that “safe unit” is yet because I haven’t read the thread too closely)


#10

Thanks for the suggestions. Transmute is an option and I did ask for it, but that is too uncomfortable to place in the chunking code. I want that part of be safe. Is there an unsafe way to just compare the memory locations of structures, in the test case of the code? In the

    let mut c = 0;
    v2.iter().for_each(|x| {
        // maybe here instead we cmp raw ptrs
        *x == v_to_cmp[c..c+x.len()];
        
        c += x.len();
    });

unsafe here is acceptable, because I know here I will not mutate the data. But outside, the safety rules for borrowing resume.


#11

I think you are on the right track; perhaps you can use .as_ptr() on the slice before the loop, and offset it and compare to .as_ptr() of each slice in the loop?

If you use wrapping_offset, this may not even require unsafe!

P.S. don’t forget to add back that assert!. I was wondering what this code was supposed to do! :stuck_out_tongue:


#12

To be clear, I’m not encouraging it (at least not my intention) :slight_smile:; it’s merely an option with some explanation. I was on mobile and that was shorter to write than the equivalent:

let v2: Vec<&[u32]> = v
        .chunks_mut(2)
        .map(|c| {
            let s = &*c as *const _;
            unsafe { &*s }
        }).collect();

#13

Please, DO NOT transmute &T to &mut T. Modifying value under shared reference which is not inside of UnsafeCell is undefined behavior in Rust. This means it is explicitly stated that compiler can arbitrary modify such code, including to launch a missile to your head!

Writing unsafe code without touching undefined behavior is really hard. Basically this is wht we highly discourage to write unsafe code at all, except that when you definitely sure what you’re doing at the assembly level and there’s absolutely no way to archive your goal in safe rust.


#14

unsafe blocks do not allow violating aliasing/borrowing rules - you must create a raw ptr at a place where you still have the correct borrowing situation. Inside the loop there you cannot borrow v, even to create a raw pointer to it. So you must do that outside the loop. If you do it outside the loop, you may as well create a raw pointer to the Vec itself, and then you’d have:

let mut v: Vec<u32> = vec![1, 2, 3, 4];
    let ptr_v: *const Vec<u32> = &v as *const _;
    let v2: Vec<&[u32]> = v.chunks_mut(2).map(|c| &*c).collect();

    let mut c = 0;
    v2.iter().for_each(|x| {
        let v = unsafe { &*ptr_v };
        assert!(*x == &v[c..c + x.len()]);
        c += x.len();
    });

IMO, you’re already barking up the wrong tree - just cloning the Vec is the easiest and safest solution (and given this is a test, I doubt the perf implications of the clone/allocation matter). I don’t see what using as_ptr() and offsetting the pointer really buys you - you’re still dealing with raw pointers and those are outside the purview of borrowck, and it muddies up the code further.


#15

No such transmute was done here.


#16

Oh, your right. It seems that something unsafe just triggered my mental fire alarm and make it screaming loudly with “FREEZE! FREEZE! STOP USING THAT!” :stuck_out_tongue:

Anyway, I found OP is just trying to collect ChunksMut into Vec<&[u32]>, not Vec<&mut [u32]>. I guess it should be enough to replace chunks_mut with chunks method to make that code compile.


#17

This part was mentioned upthread as well :slight_smile:. The tl;dr; of it is that, AFAIUI, OP has code using chunks_mut for some real purpose, and wants to reuse it for testing purposes.

So AFAICT, coupled with not wanting to clone the Vec, @seph is really forcing the unsafe hand :stuck_out_tongue_winking_eye: .