将Vec<u16>或Vec<WCHAR>转换为&str

8
我是一个有用的助手,可以翻译文本。
我正在学习Rust编程,想实现一个小程序,但在字符串转换方面有些困惑。
在我的程序中,我有以下向量:
let mut name: Vec<winnt::WCHAR> = Vec::new(); 

WCHAR 在我的 Windows 机器上相当于 u16

我将 Vec<u16> 作为指针传递给一个 C 函数(用于填充数据)。然后,我需要将向量中包含的字符串转换为 &str。但是,无论我尝试什么,都无法使此转换工作。

我唯一成功的是将其转换为 WideString

 widestr = unsafe { WideCString::from_ptr_str(name.as_ptr()) };

但这似乎是朝着错误的方向迈出的一步。
假设该向量包含一个有效且以空字符结尾的字符串,什么是将 Vec<u16> 转换为 &str 的最佳方式?
2个回答

13
我需要将向量中包含的字符串转换为&str类型,但是不管我尝试什么方法,都无法使这种转换起作用。将UTF-16编码的字符串转换成基于字节的编码方式编码的&str类型是不可能的。如果你有一个UTF-16(或者不同但常见的UCS-2编码),那么就没有办法将它读取为另一种编码方式。这相当于试图将JPEG图像读取为PDF文件。两个数据块可能都是字符串,但编码方式很重要。首先需要问自己的问题是:"你真的需要那样做吗?"。大多数情况下,你可以从一个函数中获取数据并将其放回另一个函数中,而不必查看它。如果可以这样做,那可能是最好的答案。如果你确实需要进行转换,那么你必须处理可能会出现的错误。任意的16位整数数组可能不是有效的UTF-16或UCS-2编码方式。这些编码方式具有可能产生无效字符串的边缘情况。空值终止是另一个方面——Unicode实际上允许嵌入NUL字符,因此空值终止的字符串不能包含所有可能的Unicode字符!一旦你确保了编码的有效性并确定了输入向量中有多少条目包含该字符串,那么你就必须解码输入格式并重新编码为输出格式。这可能需要某种新的分配方式,因此最有可能得到一个String类型,它可以在大多数需要&str类型的情况下使用。有一种内置方法可以将UTF-16数据转换为StringString::from_utf16。请注意,它会返回一个Result以允许处理这些错误情况。还有一种叫做String::from_utf16_lossy,它会用Unicode替换字符替换无效的编码部分。
let name = [0x68, 0x65, 0x6c, 0x6c, 0x6f]; 

let a = String::from_utf16(&name);
let b = String::from_utf16_lossy(&name);

println!("{:?}", a);
println!("{:?}", b);
如果您从指向 u16WCHAR 的指针开始,您需要先使用 slice::from_raw_parts 方法将其转换为切片。如果您有一个以 null 结尾的字符串,则需要自行查找 NUL 并适当地对输入进行分割。
&str 实际上是使用类型的好方法;它保证了编码为 UTF-8,因此无需进行进一步的检查。类似地,WideCString 在构造时可能会执行一次检查,然后可以在后续使用中跳过检查。

1
非常感谢您的帮助,我显然完全迷失了方向,还以某种方式假定两种类型的编码是相同的。在这种情况下,使用另一个字符串对象(它们实际上在Rust中被称为对象吗?)进行转换是有意义的。 - Norbert
2
@Norbert:我不确定Rust程序员是否会考虑是否称呼事物为对象;每个人都理解这个术语,所以它已经足够好了 :) - Matthieu M.
@Norbert,我认为这取决于您希望称之为“对象”的内容是什么。如果您指的是一块数据及其相关方法,那么它确实是一个对象。通常我会说“类型”而不是“类”,然后说是“类型的实例”。我发现自己并不经常需要说“对象”。不过我认为每个人都能很好地理解您的意思。 - Shepmaster
很高兴看到Rustaceans对这些词汇或语言方面并不那么教条。 :-) - Norbert

2

这是我针对这种情况的简单技巧。一定存在某个漏洞,适用于你自己的案例:

let mut v = vec![0u16; MAX_PATH as usize];

// imaginary win32 function
win32_function(v.as_mut_ptr());

let mut path = String::new();
for val in v.iter() {
    let c: u8 = (*val & 0xFF) as u8;
    if c == 0 {
        break;
    } else {
        path.push(c as char);
    }
}

4
我不知道为什么你要费心去不支持非ASCII值,但我会这样写:let path: String = v.iter().map(|&v| (v & 0xFF) as u8).take_while(|&c| c != 0).map(|c| c as char).collect(); - Shepmaster
谢谢提供的信息,我对 Rust、Scala 和 Elixir 都只是新手。 - sailfish009

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