How unsafe is this transitory view of a Vec through a *mut?

Hi All.

I've run into a small problem. I have a Vec I want to interpret as a matrix, but I can't instantiate the Vec as a matrix in the library I'm using because I still need to be able to add more data to it. Thus, I'm temporarily construction a slice of the matrix using the raw pointer. Is this safe, or actually unsafe?

fn do_things<'a>(&'a mut self) {
    unsafe {
      // slightly risky way to view self.dat as a matrix

      // get a mut ref to dat
      // really for some 'b: 'a
      let dat = &mut self.dat;

      // This should have lifetime 'c: 'b, but we can't express that here
      // (NB: at least, I think I can't? If there's a way to do this, that would be very handy.)
      let mut m : MatrixSliceMut<'_, f64> = {
        MatrixSliceMut::from_raw_parts(
          eqns.as_mut_ptr(),
          self.nrows(),
          self.ncols(),
          self.ncols(),
        )
      };

      //
      // ... do stuff with `m` ...
      //

      // ensure dat is used last, so the borrow checker warns us if we try to use `self.dat`.
      // Should be compiled to a nop.
      { dat; () }
    }
}

Ideally, I'd have some way to name fresh lifetimes 'c and 'b such that 'c <= 'b <= 'a, which would ensure that the slice does not outlive the borrow of the field. But I don't know how to do that, if it's possible. (At least, not without spinning it off into its own function.) Thus I'm worried that the compiler might do something unexpected (like an optimisation) because it doesn't infer a relationship between dat and m.

Thanks!

P.S.
The MatrixSliceMut is as follows

pub struct MatrixSliceMut<'a, T: 'a> {
    ptr: *mut T,
    rows: usize,
    cols: usize,
    row_stride: usize,
    marker: PhantomData<&'a mut T>,
}

The library I'm using is rulinalg, for reference. But hopefully there's enough detail here that you don't have to dive in to the implementation.

This function should be sound as it upholds the safety comment:

fn as_matrix_view_mut<'a, T>(data: &'a mut [T], rows: usize, cols: usize) -> MatrixSliceMut<'a, T> {
    let row_stride = cols;
    assert!(rows.checked_mul(row_stride).unwrap() <= data.len());
    unsafe {
        // SAFETY:
        //   The pointer points to a contiguous slice of data larger than row_stride * rows
        MatrixSliceMut::from_raw_parts(data.as_mut_ptr(), rows, cols, row_strides)
    }
    
}

Edit: In general, if you have to use unsafe try to slice the usage into safe and sound interfaces. The as_matrix_view_mut function above does not deal with the fact that the data is in a Vec which is embedded in another struct. It just takes a mutable slice reference and returns a mutable matrix view with the same lifetime. I am surprised that rulinalg does not provide this interface (maybe it does somewhere well hidden).

3 Likes

Why? Abstracting the matrix-constructing part as a separate function would be the best approach here. You'd be able to properly specify the lifetime bounds, and as far as I can tell the function would even be safe, containing a small unsafe block inside.

Yes I agree. In the end, it's probably the approach I'll take. It just slightly annoys me, because the ideal place to implement this function would be in the impl of MatrixSliceMut, not as a static function. (Which means, I guess, that I'll be filing a pull request. :stuck_out_tongue:)

Ah

This library is no longer actively maintained

(GitHub - AtheMathmo/rulinalg: A linear algebra library written in Rust)

Guess I'm finding a different library instead...