【问题标题】:Produce Option<Vec<String>> out of an Option<Vec<Custom>> in Rust从 Rust 中的 Option<Vec<Custom>> 生成 Option<Vec<String>>
【发布时间】:2017-01-21 00:19:31
【问题描述】:

我正在尝试在 Rust 中做相当于 Ruby 的 Enumerable.collect()

我有一个Option&lt;Vec&lt;Attachment&gt;&gt;,我想从中创建一个Option&lt;Vec&lt;String&gt;&gt;,在None guid 的情况下使用String::new() 元素。

#[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() 或其他方式来做到这一点?

【问题讨论】:

    标签: rust


    【解决方案1】:

    您应该阅读并记住Option(和Result)上的所有方法。这些在 Rust 中被如此普遍地使用,以至于知道存在什么将极大地帮助你。

    例如,您的match 语句是Option::map

    既然你从来没有说过你不能转让Strings 的所有权,我就这么做。这将避免任何额外的分配:

    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 转换为&amp;str,提供默认值。我们同样不拥有ovOption 的所有权,因此需要迭代引用,并最终用ToOwned 分配新的Strings。

    【讨论】:

      【解决方案2】:

      这是一个可行的解决方案:

      #[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&lt;T&gt; 的 trait 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&lt;T&gt; 的特征实现之后。

      Here it is running on the playground.

      【讨论】:

      • and_then 带有硬编码的 Some 并没有什么意义;应该只是map,不是吗?
      • 你必须克隆——不是拥有,而是“可以”,也许吧。
      • 另外,我很确定您会创建每个字符串两次;一次调用String::new,然后再次调用clone。空的String 并不可怕,因为没有堆分配,但总的来说很奇怪。
      • 哦,当然:/我会将其更改为map。也许我也会改写“必须”部分。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-26
      • 1970-01-01
      • 1970-01-01
      • 2018-08-27
      • 1970-01-01
      相关资源
      最近更新 更多