【问题标题】:Is there an owned version of String::chars?是否有 String::chars 的自有版本?
【发布时间】:2018-04-21 23:01:26
【问题描述】:

以下代码无法编译:

use std::str::Chars;

struct Chunks {
    remaining: Chars,
}

impl Chunks {
    fn new(s: String) -> Self {
        Chunks {
            remaining: s.chars(),
        }
    }
}

错误是:

error[E0106]: missing lifetime specifier
 --> src/main.rs:4:16
  |
4 |     remaining: Chars,
  |                ^^^^^ expected lifetime parameter

Chars 不拥有它迭代的字符,它不能比创建它的 &strString 寿命长。

是否有不需要生命周期参数的 Chars 的自有版本,还是我必须自己保留 Vec<char> 和索引?

【问题讨论】:

    标签: string iterator rust ownership


    【解决方案1】:

    std::vec::IntoIter 在某种意义上是每个迭代器的拥有版本。

    use std::vec::IntoIter;
    
    struct Chunks {
        remaining: IntoIter<char>,
    }
    
    impl Chunks {
        fn new(s: String) -> Self {
            Chunks {
                remaining: s.chars().collect::<Vec<_>>().into_iter(),
            }
        }
    }
    

    Playground link

    缺点是额外的分配和空间开销,但我不知道您的具体情况的迭代器。

    【讨论】:

      【解决方案2】:

      衔尾蛇

      您可以使用ouroboros crate 创建一个包含StringChars 迭代器的自引用结构:

      use ouroboros::self_referencing; // 0.4.1
      use std::str::Chars;
      
      #[self_referencing]
      pub struct IntoChars {
          string: String,
          #[borrows(string)]
          chars: Chars<'this>,
      }
      
      // All these implementations are based on what `Chars` implements itself
      
      impl Iterator for IntoChars {
          type Item = char;
      
          #[inline]
          fn next(&mut self) -> Option<Self::Item> {
              self.with_mut(|me| me.chars.next())
          }
      
          #[inline]
          fn count(mut self) -> usize {
              self.with_mut(|me| me.chars.count())
          }
      
          #[inline]
          fn size_hint(&self) -> (usize, Option<usize>) {
              self.with(|me| me.chars.size_hint())
          }
      
          #[inline]
          fn last(mut self) -> Option<Self::Item> {
              self.with_mut(|me| me.chars.last())
          }
      }
      
      impl DoubleEndedIterator for IntoChars {
          #[inline]
          fn next_back(&mut self) -> Option<Self::Item> {
              self.with_mut(|me| me.chars.next_back())
          }
      }
      
      impl std::iter::FusedIterator for IntoChars {}
      
      // And an extension trait for convenience
      
      trait IntoCharsExt {
          fn into_chars(self) -> IntoChars;
      }
      
      impl IntoCharsExt for String {
          fn into_chars(self) -> IntoChars {
              IntoCharsBuilder {
                  string: self,
                  chars_builder: |s| s.chars(),
              }
              .build()
          }
      }
      

      另见:

      出租

      您可以使用rental crate 创建一个包含StringChars 迭代器的自引用结构:

      #[macro_use]
      extern crate rental;
      
      rental! {
          mod into_chars {
              pub use std::str::Chars;
      
              #[rental]
              pub struct IntoChars {
                  string: String,
                  chars: Chars<'string>,
              }
          }
      }
      
      use into_chars::IntoChars;
      
      // All these implementations are based on what `Chars` implements itself
      
      impl Iterator for IntoChars {
          type Item = char;
      
          #[inline]
          fn next(&mut self) -> Option<Self::Item> {
              self.rent_mut(|chars| chars.next())
          }
      
          #[inline]
          fn count(mut self) -> usize {
              self.rent_mut(|chars| chars.count())
          }
      
          #[inline]
          fn size_hint(&self) -> (usize, Option<usize>) {
              self.rent(|chars| chars.size_hint())
          }
      
          #[inline]
          fn last(mut self) -> Option<Self::Item> {
              self.rent_mut(|chars| chars.last())
          }
      }
      
      impl DoubleEndedIterator for IntoChars {
          #[inline]
          fn next_back(&mut self) -> Option<Self::Item> {
              self.rent_mut(|chars| chars.next_back())
          }
      }
      
      impl std::iter::FusedIterator for IntoChars {}
      
      // And an extension trait for convenience 
      
      trait IntoCharsExt {
          fn into_chars(self) -> IntoChars;
      }
      
      impl IntoCharsExt for String {
          fn into_chars(self) -> IntoChars {
              IntoChars::new(self, |s| s.chars())
          }
      }
      

      另见:

      【讨论】:

        【解决方案3】:

        还有owned-chars crate

        使用 into_chars 和 into_char_indices 两种方法为 String 提供扩展特征。这些方法并行 String::chars 和 String::char_indices,但它们创建的迭代器使用 String 而不是借用它。

        【讨论】:

          【解决方案4】:

          您可以实现自己的迭代器,或者像这样包装Chars(只有一个小的unsafe 块):

          // deriving Clone would be buggy. With Rc<>/Arc<> instead of Box<> it would work though.
          struct OwnedChars {
              // struct fields are dropped in order they are declared,
              // see https://stackoverflow.com/a/41056727/1478356
              // with `Chars` it probably doesn't matter, but for good style `inner`
              // should be dropped before `storage`.
          
              // 'static lifetime must not "escape" lifetime of the struct
              inner: ::std::str::Chars<'static>,
              // we need to box anyway to be sure the inner reference doesn't move when
              // moving the storage, so we can erase the type as well.
              // struct OwnedChar<S: AsRef<str>> { ..., storage: Box<S> } should work too
              storage: Box<AsRef<str>>,
          }
          
          impl OwnedChars {
              pub fn new<S: AsRef<str>+'static>(s: S) -> Self {
                  let storage = Box::new(s) as Box<AsRef<str>>;
                  let raw_ptr : *const str = storage.as_ref().as_ref();
                  let ptr : &'static str = unsafe { &*raw_ptr };
                  OwnedChars{
                      storage: storage,
                      inner: ptr.chars(),
                  }
              }
          
              pub fn as_str(&self) -> &str {
                  self.inner.as_str()
              }
          }
          
          impl Iterator for OwnedChars {
              // just `char` of course
              type Item = <::std::str::Chars<'static> as Iterator>::Item;
          
              fn next(&mut self) -> Option<Self::Item> {
                  self.inner.next()
              }
          }
          
          impl DoubleEndedIterator for OwnedChars {
              fn next_back(&mut self) -> Option<Self::Item> {
                  self.inner.next_back()
              }
          }
          
          impl Clone for OwnedChars {
              fn clone(&self) -> Self {
                  // need a new allocation anyway, so simply go for String, and just
                  // clone the remaining string
                  OwnedChars::new(String::from(self.inner.as_str()))
              }
          }
          
          impl ::std::fmt::Debug for OwnedChars {
              fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                  let storage : &str = self.storage.as_ref().as_ref();
                  f.debug_struct("OwnedChars")
                      .field("storage", &storage)
                      .field("inner", &self.inner)
                      .finish()
              }
          }
          
          // easy access
          trait StringExt {
              fn owned_chars(self) -> OwnedChars;
          }
          impl<S: AsRef<str>+'static> StringExt for S {
              fn owned_chars(self) -> OwnedChars {
                  OwnedChars::new(self)
              }
          }
          

          playground

          【讨论】:

          • The same thing,但使用rental crate。不幸的是,它在操场上不起作用。
          • 为什么需要额外的盒子? S 只能是 StringBox&lt;str&gt; 或其他某种拥有 str 的引用,对吧?因此存储必须是堆分配的(如果不是'static),因此在删除S 之前不会移动。 (只要OwnedChars 没有在push 上启动或以其他方式触发移动。)
          • 我可以创建一个带有小字符串优化的字符串存储类型(参见smallveccreate)。
          • @Stefan 啊,真的。但似乎这个结构的正常用途是当你手头有一个String 并且在这种情况下它是双盒装的。您认为存储Box&lt;str&gt; 并拥有new&lt;S: Into&lt;Box&lt;str&gt;&gt;&gt; 是否安全?这适用于任何参考以及拥有的Strings,仅在必要时复制内容,并且不会双框。
          • 我不确定将String 转换为Box&lt;str&gt; 的分配开销——如果它重用Vec 内存,这应该会更快,是的。如果您知道您只想为Strings 执行此操作,那么您当然也可以使用它(未装箱) - afaict String 保证堆分配。
          【解决方案5】:

          复制自How can I store a Chars iterator in the same struct as the String it is iterating on?:

          use std::mem;
          use std::str::Chars;
          
          /// I believe this struct to be safe because the String is
          /// heap-allocated (stable address) and will never be modified
          /// (stable address). `chars` will not outlive the struct, so
          /// lying about the lifetime should be fine.
          ///
          /// TODO: What about during destruction?
          ///       `Chars` shouldn't have a destructor...
          struct OwningChars {
              _s: String,
              chars: Chars<'static>,
          }
          
          impl OwningChars {
              fn new(s: String) -> Self {
                  let chars = unsafe { mem::transmute(s.chars()) };
                  OwningChars { _s: s, chars }
              }
          }
          
          impl Iterator for OwningChars {
              type Item = char;
              fn next(&mut self) -> Option<Self::Item> {
                  self.chars.next()
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-10-18
            • 2011-11-05
            • 2012-05-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多