从Vec<char>创建字符串

10

我有一个 Vec<char>,需要转换为 &strString,但我不确定最佳的方法是什么。我查找了许多资源,但每个答案都似乎有些过时。在这个问题中的回答似乎不适用于最新版本。

我正在使用2015-3-19的夜间版本。

3个回答

30

使用基于迭代器的方法和.collect应该可以正常工作,在更新语言更改后:

char_vector.iter().cloned().collect::<String>();

(我选择用.cloned()替换.map(|c| *c),但两种写法都可以。)


10

如果您的向量可以被使用,您也可以使用into_iter来避免clone

fn main() {
    let char_vector = vec!['h', 'e', 'l', 'l', 'o'];
    let str: String = char_vector.into_iter().collect();

    println!("{}", str);
}

在这种情况下,克隆的是char,这是非常便宜的(它将被编译成指针解引用,从内存中获取char到(4字节)寄存器)。当然,避免克隆对于性能通常很重要,但char并不是这样一种类型,放弃对Vec的控制可能是不可取的。 - huon
1
不知道超便宜的部分。只是想提供克隆的替代方案,可以在其他不那么便宜的情况下有所帮助。 - tafia

7
你可以将 Vec 转化为 String 而不进行任何分配。但这需要相当多的不安全代码:
#![feature(raw, unicode)]
use std::raw::Repr;
use std::slice::from_raw_parts_mut;

fn inplace_to_string(v: Vec<char>) -> String {
    unsafe {
        let mut i = 0;
        {
            let ch_v = &v[..];
            let r = ch_v.repr();
            let p: &mut [u8] = from_raw_parts_mut(r.data as *mut u8, r.len*4);
            for ch in ch_v {
                i += ch.encode_utf8(&mut p[i..i+4]).unwrap();
            }
        }
        let p = v.as_ptr();
        let cap = v.capacity()*4;
        std::mem::forget(v);
        let v = Vec::from_raw_parts(p as *mut u8, i, cap);
        String::from_utf8_unchecked(v)
    }
}

fn main() {
    let char_vector = vec!['h', 'ä', 'l', 'l', 'ö'];
    let str: String = char_vector.iter().cloned().collect();
    let str2 = inplace_to_string(char_vector);

    println!("{}", str);
    println!("{}", str2);
}

PlayPen

详细解释

这将同时创建一个可变的u8切片和一个char切片,它们指向同一个缓冲区(破坏了Rust的所有保证)。请注意,u8切片的大小是char切片的四倍,因为char始终占用4个字节。

let ch_v = &v[..];
let r = ch_v.repr();
let v: &mut [u8] = from_raw_parts_mut(r.data as *mut u8, r.len*4);

我们需要这样做来迭代Unicode字符并将它们替换为它们的UTF8编码。由于UTF8始终比Unicode短或者长度相同,我们可以保证我们永远不会覆盖任何我们尚未读取的部分。
for ch in ch_v {
    i += ch.encode_utf8(&mut v[i..i+4]).unwrap();
}

由于char总是unicode,而我们的缓冲区始终恰好为4个字节(这是utf8编码的unicode字符所需的最大字节数),因此我们可以将字符编码为utf8而无需检查是否成功(它总是可行的)。encode_utf8函数返回utf8表示的长度。我们的索引i是最后一个写入的utf8字符的位置。

最后,我们需要做一些清理工作。我们的向量仍然是Vec<char>类型。我们获取了所有所需的信息(指向堆分配数组的指针和容量)。

let p = v.as_ptr();
let cap = v.capacity()*4;

然后我们释放上一个向量所承担的所有义务,如释放内存等。

std::mem::forget(v);

最后,重新创建正确长度和容量的u8向量,并将其直接转换为字符串。不需要检查转换为字符串,因为我们已经知道utf8是正确的,因为原始的Vec<char>只包含正确的Unicode字符。

let v = Vec::from_raw_parts(p as *mut u8, i, cap);
String::from_utf8_unchecked(v)

哦,对了,我误读了构造函数(它将容量乘以元素大小,但仅将其用于分配大小,而不是容量)。好吧,现在我们有一个有教育意义的例子,说明为什么不应轻易使用unsafe;-) 修复应该很容易,参见Vec::from_raw_parts的文档。 - user395760
巧妙的技巧。"由于我们的字符肯定是Unicode,所以我们可以将它们编码为UTF8而无需检查是否成功":顺便说一下,encode_utf8作用于char,这些字符始终是有效的Unicode:错误在于输入缓冲区太短(例如,尝试将4字节代码点写入 &mut v[i..i + 2])。 - huon
谢谢,已添加该信息。 - oli_obk

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