我试图在Rust中做相当于Ruby的Enumerable.collect()
。
我有一个Option<Vec<Attachment>>
,我希望从中创建一个Option<Vec<String>>
,String::new()
guid的情况下会None
个元素。
#[derive(Debug)]
pub struct Attachment {
pub guid: Option<String>,
}
fn main() {
let ov: Option<Vec<Attachment>> =
Some(vec![Attachment { guid: Some("rere34r34r34r34".to_string()) },
Attachment { guid: Some("5345345534rtyr5345".to_string()) }]);
let foo: Option<Vec<String>> = match ov {
Some(x) => {
x.iter()
.map(|&attachment| attachment.guid.unwrap_or(String::new()))
.collect()
}
None => None,
};
}
编译器中的错误很明显:
error[E0277]: the trait bound `std::option::Option<std::vec::Vec<std::string::String>>: std::iter::FromIterator<std::string::String>` is not satisfied
--> src/main.rs:15:18
|
15 | .collect()
| ^^^^^^^ the trait `std::iter::FromIterator<std::string::String>` is not implemented for `std::option::Option<std::vec::Vec<std::string::String>>`
|
= note: a collection of type `std::option::Option<std::vec::Vec<std::string::String>>` cannot be built from an iterator over elements of type `std::string::String`
如果我记得我到目前为止从文档中读到的内容,我无法实现我不拥有的struct
特征。
如何使用iter().map(...).collect()
或其他方式执行此操作?
答案 0 :(得分:4)
您应该阅读并记住Option
(和Result
)上的所有方法。这些在Rust中使用如此普遍,知道存在什么会对你有很大的帮助。
例如,您的match
语句为Option::map
。
由于您从未说过无法转移String
的所有权,我只是这样做。这将避免任何额外的分配:
let foo: Option<Vec<_>> =
ov.map(|i| i.into_iter().map(|a| a.guid.unwrap_or_else(String::new)).collect());
请注意,我们不必在Vec
内指定类型;可以推断出来。
您当然可以引入功能以使其更清洁:
impl Attachment {
fn into_guid(self) -> String {
self.guid.unwrap_or_else(String::new)
}
}
// ...
let foo: Option<Vec<_>> = ov.map(|i| i.into_iter().map(Attachment::into_guid).collect());
如果您不想放弃String
的所有权,您可以使用字符串切片执行相同的概念:
impl Attachment {
fn guid(&self) -> &str {
self.guid.as_ref().map_or("", String::as_str)
}
}
// ...
let foo: Option<Vec<_>> = ov.as_ref().map(|i| i.iter().map(|a| a.guid().to_owned()).collect());
在此,我们必须使用Option::as_ref
来避免将guid
移出Attachment
,然后使用String::as_str
转换为&str
,默认值。我们同样不承担Option
ov
的所有权,因此需要对引用进行迭代,并最终使用String
分配新的ToOwned
。
答案 1 :(得分:1)
这是一个有效的解决方案:
#[derive(Debug)]
pub struct Attachment {
pub guid: Option<String>,
}
fn main() {
let ov: Option<Vec<Attachment>> =
Some(vec![Attachment { guid: Some("rere34r34r34r34".to_string()) },
Attachment { guid: Some("5345345534rtyr5345".to_string()) }]);
let foo: Option<Vec<_>> = ov.map(|x|
x.iter().map(|a| a.guid.as_ref().unwrap_or(&String::new()).clone()).collect());
println!("{:?}", foo);
}
上述代码的一个问题是阻止guid
移出Attachment
并进入向量。我的示例调用clone
将克隆的实例移动到向量中。
这样可行,但我认为它看起来更好,包含在Option<T>
的特征impl中。也许这是一个更好的选择......:
trait CloneOr<T, U>
where U: Into<T>,
T: Clone
{
fn clone_or(&self, other: U) -> T;
}
impl<T, U> CloneOr<T, U> for Option<T>
where U: Into<T>,
T: Clone
{
fn clone_or(&self, other: U) -> T {
self.as_ref().unwrap_or(&other.into()).clone()
}
}
#[derive(Debug)]
pub struct Attachment {
pub guid: Option<String>,
}
fn main() {
let ov: Option<Vec<Attachment>> =
Some(vec![Attachment { guid: Some("rere34r34r34r34".to_string()) },
Attachment { guid: Some("5345345534rtyr5345".to_string()) },
Attachment { guid: None }]);
let foo: Option<Vec<_>> =
ov.map(|x| x.iter().map(|a| a.guid.clone_or("")).collect());
println!("{:?}", foo);
}
基本上,解包和克隆隐藏在附加到Option<T>
的特征实现背后。