生成选项<vec <string>&gt;超出选项<vec <custom>&gt;在Rust

时间:2017-01-21 00:19:32

标签: rust

我试图在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()或其他方式执行此操作?

2 个答案:

答案 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>的特征实现背后。

Here it is running on the playground