在Rust中将i8的向量转换为u8的向量?

19
有没有比这两种方法更好的方式在Rust中将Vec<i8>转换为Vec<u8>?(1)通过映射和转换每个条目来创建一个副本非常慢,(2)使用std::transmute,但是根据docs,“transmute 应该是绝对的最后手段”。背景信息:我从不安全的gl :: GetShaderInfoLog()调用中获取了Vec<i8>,并希望通过使用String :: from_utf8()从此字符向量创建字符串。

这似乎是相关的:https://github.com/nukep/rust-opengl-util/blob/fc30c6e386b0a4510564f242d995c845472207d3/shader.rs#L56。 - sshashank124
这段代码你会经常调用吗?值得冒着使用 transmute 和 from_raw_parts 这样不安全机制的风险去折腾吗?通常情况下,你并不会频繁重新编译着色器... - KillianDS
3个回答

19
其他回答提供了从Vec<i8>创建字符串的优秀解决方案。针对所提出的问题,可以在不复制或转换向量的情况下,从Vec<i8>中创建Vec<u8>。正如@trentcl指出的那样,直接转换向量构成未定义行为,因为Vec允许具有不同类型的不同布局。
正确(尽管仍需要使用unsafe)的传输向量数据而不复制它的方法是:
  • 获取向量中数据的*mut i8指针,以及其长度和容量。
  • 泄漏原始向量以防止释放数据。
  • 使用Vec::from_raw_parts构建一个新向量,将指针强制转换为*mut u8——这是不安全的部分,因为我们保证指针包含有效且初始化的数据,并且它没有被其他对象使用等。
由于新的Vec从一开始就获得了正确类型的指针,因此这不会导致未定义行为。代码(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]);
}

好的回答,“轻微”的更正:指针转换(+ from_raw_parts)不仅比transmute“稍微安全一点”,因为(AIUI)从Vec<T>Vec<U>transmute始终是未定义行为。两者都使用unsafe,但from_raw_parts正确的,而transmute不正确的 - trent
@trentcl 谢谢,我已经更新了答案。(在这里,我想比较一下将类型转换为指针的想法,这是我在早期草稿中删除后发布的。)然而,我很好奇为什么从 Vec<i8> 转换为 Vec<u8> 会导致 UB,而使用从 *mut i8*mut u8 的强制转换获得指针创建新的 Vec<u8> 是合法的?这是因为 Vec<T> 的表示可能取决于未来的 T 类型,还是有些东西使它与当前 Rust 或 LLVM 的抽象机器模型不符合规范? - user4815162342
2
仅仅因为 Vec<T> 的表示可能取决于 T,虽然不太可能,但是确实是允许的。我承认我很难想象出一种情况,可以利用这个特性。 - trent
1
@trentcl 同意,Vec 可能不是最好的例子,但我可以想象一个存储有关通用类型信息的数据结构,例如为了防止单态化引起的代码膨胀。尽管如此,我还是想知道我们能够采取多少谨慎措施。例如,将 Vec<u8>::from_raw_parts 传递给一个最初通过请求 *mut i8 分配数据的 *mut u8 指针是否属于 UB?如果有更清晰的文档说明什么构成了 Rust 不安全的行为,那将会很有帮助。 - user4815162342

11

Vec 上的 transmute 操作总是100%不正确的,会导致未定义的行为,因为 Vec 的布局未指定。但是,正如您链接的页面也提到的那样,您可以使用裸指针和Vec::from_raw_parts 正确执行此操作。 user4815162342的回答 展示了如何做到这一点。

(std::mem::transmute 是 Rust 标准库中唯一文档主要由建议组成的项目,用于说明如何使用它。按照您的方式进行采取。)

然而,在这种情况下,from_raw_parts 也是不必要的。在 Rust 中处理 C 字符串的最佳方法是使用 std::ffi 中的包装器,CStrCString。可能有更好的方法将其应用到实际代码中,但以下是一种使用CStrVec<c_char> 借用为 &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");

注意除了调用FFI函数外,没有任何不安全的代码。你也可以使用with_capacity + set_len(如wasmup的答案中所述)跳过将 Vec 初始化为1000个零,并使用from_bytes_with_nul_unchecked跳过检查返回字符串的有效性。


2
  1. 请看这里
fn get_compilation_log(&self) -> String {
    let mut len = 0;
    unsafe { gl::GetShaderiv(self.id, gl::INFO_LOG_LENGTH, &mut len) };
    assert!(len > 0);

    let mut buf = Vec::with_capacity(len as usize);
    let buf_ptr = buf.as_mut_ptr() as *mut gl::types::GLchar;
    unsafe {
        gl::GetShaderInfoLog(self.id, len, std::ptr::null_mut(), buf_ptr);
        buf.set_len(len as usize);
    };

    match String::from_utf8(buf) {
        Ok(log) => log,
        Err(vec) => panic!("Could not convert compilation log from buffer: {}", vec),
    }
}

  1. See ffi:
let s = CStr::from_ptr(strz_ptr).to_str().unwrap();

文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接