Unsafe code confusion

Hello all,

I have been reading the great "Rust for Rustaceans" book and I'm currently in the unsafe chapter of the book. What I am currently getting from it is that you have to take the upmost caution when performing casting between pointer types and can never assume the layout of the structures.

Today I was checking out some documentation about transmute in std::mem and found this example, recommending the use of from_raw_parts instead of transmute.

let store = [0, 1, 2, 3];
let v_orig = store.iter().collect::<Vec<&i32>>();

// clone the vector as we will reuse them later
let v_clone = v_orig.clone();

// Using transmute: this relies on the unspecified data layout of `Vec`, which is a
// bad idea and could cause Undefined Behavior.
// However, it is no-copy.
let v_transmuted = unsafe {
    std::mem::transmute::<Vec<&i32>, Vec<Option<&i32>>>(v_clone)
};

let v_clone = v_orig.clone();

// This is the suggested, safe way.
// It does copy the entire vector, though, into a new array.
let v_collected = v_clone.into_iter()
                         .map(Some)
                         .collect::<Vec<Option<&i32>>>();

let v_clone = v_orig.clone();

// This is the proper no-copy, unsafe way of "transmuting" a `Vec`, without relying on the
// data layout. Instead of literally calling `transmute`, we perform a pointer cast, but
// in terms of converting the original inner type (`&i32`) to the new one (`Option<&i32>`),
// this has all the same caveats. Besides the information provided above, also consult the
// [`from_raw_parts`] documentation.
let v_from_raw = unsafe {
    // Ensure the original vector is not dropped.
    let mut v_clone = std::mem::ManuallyDrop::new(v_clone);
    Vec::from_raw_parts(v_clone.as_mut_ptr() as *mut Option<&i32>,
                        v_clone.len(),
                        v_clone.capacity())
};

Found some comments online saying that " With Vec , you can get the desired transmute of the heap repr with into_raw_parts and from_raw_parts , but doing the direct transmute is relying on unstable implementation details.".

I understand that part, what I'm not clear is the casting from &i32 to Option<&i32>. Is it safe in that direction because we can't have null references? Then am I correct to assume casting Option<&i32> to &i32 would be considered unsafe because of the niche optimization?

TLDR: trying to understand in which contexts I can be 100% sure that my casting will not cause undefined behaviour.

Yes, the transmute from &i32 to Option<&i32> is safe because references are never null. This is called the "null pointer optimization". See the representation section in the docs for std::option.

Similarly, transmuting from Option<&i32> to &i32 is also okay as long as the Option has a Some value.

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.