What logic does the borrow checker use to analyze references derived from unsafe code?

Hello, world! I'm a long-time C++ user, starting to learn Rust because a project I'll be working on in the fall is implemented largely in Rust. I've been reading through The Book over the past several days, and very much enjoying it so far. While reading the section on unsafe code, a question occurred to me: how does the borrow checker analyze references returned by unsafe code?

As a motivating example, consider slice::split_at_mut

This code compiles and runs as one might expect:

fn main() {
    let mut data = [0, 1, 2, 3];
    let (left, right) = data.split_at_mut(2);
    println!("Left: {left:?}. Right: {right:?}");
    let data_ref = &data;
    println!("Data_ref: {data_ref:?}");    

    // Uncommenting this line results in a borrow checker error:
    // "error[E0502]: cannot borrow `data` as immutable because it is also borrowed as mutable"
    // println!("Left again: {left:?}.");   

    // Uncommenting this line *also* results in a borrow checker error.
    // println!("Right again: {right:?}.");
}

As the comments note, the borrow checker will correctly throw an error if a reference to either left or right is used after data_ref is borrowed. What logic does the borrow checker use to infer that both references returned from slice::split_at_mut are borrows of data?

1 Like

Same as the any other references - using the lifetime annotations describing their expected relations. The only difference is that inside this unsafe code one can use raw pointers which do not have these annotations and therefore are not checked, but once you create a reference with some lifetime - it is treated the same, no matter how it was created.

For a specific case of split_at_mut, it has the following signature:

fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T])

which, when using lifetime elision rules, becomes this:

fn split_at_mut<'self>(&'self mut self, mid: usize) -> (&'self mut [T], &'self mut [T])

i.e. the input reference is kept alive as long as either of the output references is alive.

3 Likes

It doesn't analyse what the unsafe code does via raw pointers. It just believes that the lifetimes created unsafely (by raw pointer dereference or transmute) are correct.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.