【问题标题】:Why is std::borrow::Cow apparently required when mapping over strings with a Regex?为什么在使用正则表达式映射字符串时显然需要 std::borrow::Cow ?
【发布时间】:2018-09-18 20:24:28
【问题描述】:

我正在 Parser 结构中实现代码解析器。我公开了一个 pub 方法 lines 来迭代删除 cmets 的代码行。我想返回一个Box<Iterator>

extern crate regex; // 1.0.5

use regex::Regex;

pub struct Parser {
    code: String,
}

static comment: Regex = Regex::new(r"//.*$").unwrap();

impl Parser {
    pub fn new(code: String) -> Parser {
        Parser { code }
    }

    pub fn lines(&self) -> Box<Iterator<Item = &str>> {
        let lines = self
            .code
            .split("\n")
            .map(|line| comment.replace_all(line, ""));
        Box::new(lines)
    }
}

但是,编译器给出了这个错误:

error[E0271]: type mismatch resolving `<[closure@src/lib.rs:20:18: 20:54] as std::ops::FnOnce<(&str,)>>::Output == &str`
  --> src/lib.rs:21:9
   |
21 |         Box::new(lines)
   |         ^^^^^^^^^^^^^^^ expected enum `std::borrow::Cow`, found &str
   |
   = note: expected type `std::borrow::Cow<'_, str>`
              found type `&str`
   = note: required because of the requirements on the impl of `std::iter::Iterator` for `std::iter::Map<std::str::Split<'_, &str>, [closure@src/lib.rs:20:18: 20:54]>`
   = note: required for the cast to the object type `dyn std::iter::Iterator<Item=&str>`

它希望我使用std::borrow::Cow,但我在the Map docs 中找不到任何提及此要求的内容。为什么这是必要的?我可以避免吗?

【问题讨论】:

  • Idiomatic Rust 使用snake_case 表示变量、方法、宏和字段; UpperCamelCase 用于类型; SCREAMING_SNAKE_CASE 用于静态和常量。请改用static COMMENT

标签: rust


【解决方案1】:

强烈建议您阅读所有您正在使用的类型和方法的文档。例如,Regex::replace_all 记录为:

pub fn replace_all<'t, R: Replacer>(
    &self, 
    text: &'t str, 
    rep: R
) -> Cow<'t, str>
//   ^^^^^^^^^^^^

这就是Cow 的来源。

一旦分配了新的字符串,就不可能返回&amp;strs 的迭代器;您将需要选择一个新的迭代器类型。像这样的事情似乎是可能的,但由于您的代码由于这个生命周期问题以外的原因无法编译,因此我无法轻松对其进行测试。

pub fn lines<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a>

另见:

【讨论】:

  • 感谢有关文档的建议。我是从 JS 过来的,它的对象很少有很好的记录。所以我习惯于在 REPL 中自省以找出对象具有哪些字段。我把这个习惯带进了 Rust。但是现在,从现在开始,我一直在使用手头的文档来开发我的 Rust,这是一种更好的体验!谢谢!
【解决方案2】:

这是我的情况的最佳解决方案。

replace_all 不是这个用例的好方法。我只想删除 cmets。我永远不需要在字符串中插入任何东西。如果是这样,我应该能够使用字符串切片。不需要replace_all引入的Cow类型。这是我的做法。

impl Parser {
    pub fn lines<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> {
        let lines = self.code
            .lines()
            .map(|line| { line.split("//").next().unwrap() })
            .map(|line| line.trim())
            .filter(|line| line.len() > 0);

        Box::new(lines)
    }
}

【讨论】:

    【解决方案3】:

    正如您已经发现的那样,Cow 来自 Regex::replace_all

    在您的情况下,有一种非常危险且不鼓励的方法来获取 &amp;str 的迭代器:

    extern crate regex; // 1.0.5
    
    use regex::Regex;
    use std::borrow::Cow;
    
    pub struct Parser {
        code: String,
    }
    
    impl Parser {
        pub fn new(code: String) -> Parser {
            Parser { code }
        }
    
        pub fn lines<'a>(&'a self, comment: Regex) -> Box<Iterator<Item = &'a str> + 'a> {
            let lines = self
                .code
                .split("\n")
                .map(move |line| comment.replace_all(line, ""))
                .map(|cow| match cow {
                    Cow::Borrowed(sref) => sref,
                    Cow::Owned(_) => panic!("I hope never to be here"),
                });
            Box::new(lines)
        }
    }
    
    fn main() {
        let comment: Regex = Regex::new(r"//.*$").unwrap();
    
        let p = Parser::new("hello\nworld".to_string());
    
        for item in p.lines(comment) {
            println!("{:?}", item);
        }
    }
    

    这是因为没有分配字符串的Cows。

    更好的方法是返回 Strings 的迭代器:

    pub fn lines<'a>(&'a self, comment: Regex) -> Box<Iterator<Item = String> + 'a> {
        let lines = self
            .code
            .split("\n")
            .map(move |line| comment.replace_all(line, ""))
            .map(|cow| cow.into_owned());
    
        Box::new(lines)
    }
    

    【讨论】:

    • 坦率地说,这是一个可怕的建议。如果永远不需要运行replace_all 调用(因此永远不需要返回Cow::Owned),那么它不应该在那里。据推测,由于某种原因,OP 实际上确实需要删除 cmets,因此这总是会恐慌。它也根本不“危险”。
    • 我也不同意返回Strings 普遍“更好”;现在您必须为每一行重新分配,这似乎是对性能的极大浪费。
    • 确实,我只需要删除cmets。副本不应该是必要的。我认为replace_all 对我来说是一个糟糕的方法选择。我发布了一个更好的方法的答案。
    猜你喜欢
    • 1970-01-01
    • 2016-06-26
    • 2021-01-19
    • 1970-01-01
    • 2017-09-05
    • 2011-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多