Cast vector of i8 to vector of u8 in Rust?
transmute
on a Vec
is always, 100% wrong, causing undefined behavior, because the layout of Vec
is not specified. However, as the page you linked also mentions, you can use raw pointers and Vec::from_raw_parts
to perform this correctly. user4815162342's answer shows how.
(std::mem::transmute
is the only item in the Rust standard library whose documentation consists mostly of suggestions for how not to use it. Take that how you will.)
However, in this case, from_raw_parts
is also unnecessary. The best way to deal with C strings in Rust is with the wrappers in std::ffi
, CStr
and CString
. There may be better ways to work this in to your real code, but here's one way you could use CStr
to borrow a Vec<c_char>
as a &str
:
const BUF_SIZE: usize = 1000;
let mut info_log: Vec<c_char> = vec![0; BUF_SIZE];
let mut len: usize;
unsafe {
gl::GetShaderInfoLog(shader, BUF_SIZE, &mut len, info_log.as_mut_ptr());
}
let log = Cstr::from_bytes_with_nul(info_log[..len + 1])
.expect("Slice must be nul terminated and contain no nul bytes")
.to_str()
.expect("Slice must be valid UTF-8 text");
Notice there is no unsafe
code except to call the FFI function; you could also use with_capacity
+ set_len
(as in wasmup's answer) to skip initializing the Vec
to 1000 zeros, and use from_bytes_with_nul_unchecked
to skip checking the validity of the returned string.
The other answers provide excellent solutions for the underlying problem of creating a string from Vec<i8>
. To answer the question as posed, creating a Vec<u8>
from data in a Vec<i8>
can be done without copying or transmuting the vector. As pointed out by @trentcl, transmuting the vector directly constitutes undefined behavior because Vec
is allowed to have different layout for different types.
The correct (though still requiring the use of unsafe
) way to transfer a vector's data without copying it is:
- obtain the
*mut i8
pointer to the data in the vector, along with its length and capacity - leak the original vector to prevent it from freeing the data
- use
Vec::from_raw_parts
to build a new vector, giving it the pointer cast to*mut u8
- this is the unsafe part, because we are vouching that the pointer contains valid and initialized data, and that it is not in use by other objects, and so on.
This is not UB because the new Vec
is given the pointer of the correct type from the start. Code (playground):
fn vec_i8_into_u8(v: Vec<i8>) -> Vec<u8> {
// ideally we'd use Vec::into_raw_parts, but it's unstable,
// so we have to do it manually:
// first, make sure v's destructor doesn't free the data
// it thinks it owns when it goes out of scope
let mut v = std::mem::ManuallyDrop::new(v);
// then, pick apart the existing Vec
let p = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();
// finally, adopt the data into a new Vec
unsafe { Vec::from_raw_parts(p as *mut u8, len, cap) }
}
fn main() {
let v = vec![-1i8, 2, 3];
assert!(vec_i8_into_u8(v) == vec![255u8, 2, 3]);
}