如何将Vec<String>转换为Vec<&str>?

91

我可以这样将 Vec<String> 转换为 Vec<&str>

let another_items = vec!["foo".to_string(), "bar".to_string()];
let mut items = Vec::<&str>::new();
for item in &another_items {
    items.push(item);
}

有没有更好的选择?

6个回答

94

有很多方法可以做到这一点,有些有缺点,有些对某些人来说更易读。

这会将 s(类型为&String)解引用为一个“右手引用”的String,然后通过Deref特质再次解引用为一个“右手引用”的str,最后转换回一个&str。这在编译器中非常常见,我认为这是惯用的。

let v2: Vec<&str> = v.iter().map(|s| &**s).collect();

这里将Deref特质的deref函数传递给map函数。这相当不错,但需要使用该特质或提供完整路径。

let v3: Vec<&str> = v.iter().map(std::ops::Deref::deref).collect();

这使用强制类型转换语法。

let v4: Vec<&str> = v.iter().map(|s| s as &str).collect();

这将对String进行RangeFull切片(仅切片到整个String),并对其引用。在我看来,这很丑陋。

let v5: Vec<&str> = v.iter().map(|s| &s[..]).collect();

这里使用强制类型转换将&String转换成&str。未来也可以用s: &str表达式替代。

let v6: Vec<&str> = v.iter().map(|s| { let s: &str = s; s }).collect();
以下内容(感谢@huon-dbaupp)使用了AsRef trait,它的唯一存在就是将拥有所有权的类型映射到它们各自的借用类型上。有两种使用方法,而且两个版本的美观程度都是完全主观的。
let v7: Vec<&str> = v.iter().map(|s| s.as_ref()).collect();
let v8: Vec<&str> = v.iter().map(AsRef::as_ref).collect();

我的底线是使用 v8 解决方案,因为它最明确地表达了你想要的。


3
直接调用deref(特别是作为自由函数)在我看来读起来相当奇怪,我个人认为v2v5更好。 (也可以使用as_refv.iter().map(| s | s.as_ref())...这是此类通用转换的典型方法,例如,std中的许多函数都采用P:AsRef <Path>。) - huon
谢谢,你说得完全正确。在这里使用AsRef更加合适。我已经更新了帖子。 - oli_obk
8
在这个v8示例中,你也可以调用String::as_str,这会让你的代码意图更加清晰。 - JeanMertz
1
s as &str 对我来说最有意义,按照惯用方式去理解和记忆在我看来很困难。 - Piotr Zakrzewski
所以,没有不分配内存的方式吗?collect 会分配一个新的向量。 - rsalmei

38

其他回答已经解决了问题。我只想指出,如果你试图将Vec<String>转换为Vec<&str>,仅仅是为了将其传递给一个以Vec<&str>作为参数的函数,请考虑修改函数签名为:

fn my_func<T: AsRef<str>>(list: &[T]) { ... }

改为:

fn my_func(list: &Vec<&str>) { ... }

如此问题所指出的:处理拥有和非拥有字符串的函数。这样两个向量可以直接使用而无需进行转换。

5
所有的答案都习惯性地使用了迭代器和收集而不是循环,但是没有解释为什么这种方法更好。
在您的循环中,首先创建一个空向量,然后将元素推入其中。Rust对于增长因子的策略没有任何保证,但我相信当前的策略是每当超出容量时,向量容量就会加倍。如果原始向量的长度为20,则需要一次分配和五次重新分配。
从向量中迭代产生一个具有“大小提示”的迭代器。在这种情况下,迭代器实现了ExactSizeIterator,因此它确切地知道它将返回多少元素。map保留了这一点,collect通过一次性为ExactSizeIterator分配足够的空间来利用这个特性。
你也可以手动操作:vec.reserve_exact(iter.len())使得向量拥有准确的内存占用。
let mut items = Vec::<&str>::with_capacity(another_items.len());
for item in &another_items {
    items.push(item);
}

堆分配和重新分配可能是整个过程中最昂贵的部分,远比获取引用、编写代码或将元素推入向量等不涉及新堆分配时更加昂贵。如果需要进行两次重新分配和一次分配才能将5个元素推入向量,则将一千个元素在向量中一次性分配可能比后者更快。
另一个不为人知的优点是使用具有“collect”方法的方法不会存储在可变变量中,如果不需要,则不应该使用它。

这似乎不是“回答问题”的方法:“如何将Vec<String>转换为Vec<&str>?” - Shepmaster
@Shepmaster 正确,这个问题已经有很多种回答了。我只是想指出所有这些回答都忽略了为什么它们的答案是符合 Rust 习惯用法而不是 ops 建议的原因。仅仅说它们更好,而没有解释为什么更好,似乎对我来说是一个很差的学习练习。 - Zorf

4
another_items.iter().map(|item| item.deref()).collect::<Vec<&str>>()

为了使用deref(),你必须添加use std::ops::Deref

4
这个例子使用了collect:
let strs: Vec<&str> = another_items.iter().map(|s| s as &str).collect();

2
这里有另一个选择:
use std::iter::FromIterator;

let v = Vec::from_iter(v.iter().map(String::as_str));

请注意,自Rust 1.7以来,String::as_str方法已经稳定可用。

FromIterator自2021版起已经在预导入中,因此现在无需导入它。但我认为你不应该在这里使用from_iter()collect()更好。 - Chayim Friedman

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