如何组合对 Regex::replace_all 的两次调用?

How to compose two calls to Regex::replace_all?

Regex::replace_all 具有签名 fn (text: &str) -> Cow<str>。如何写两个调用,f(g(x)),给出相同的签名?

这是我正在尝试编写的一些代码。这将两个调用分成两个函数,但我也无法让它在一个函数中工作。这是我的 lib.rs 在一个新的 Cargo 项目中:

#![allow(dead_code)]

/// Plaintext and HTML manipulation.

use lazy_static::lazy_static;
use regex::Regex;
use std::borrow::Cow;

lazy_static! {
    static ref DOUBLE_QUOTED_TEXT: Regex = Regex::new(r#""(?P<content>[^"]+)""#).unwrap();
    static ref SINGLE_QUOTE:       Regex = Regex::new(r"'").unwrap();
}


fn add_typography(text: &str) -> Cow<str> {
    add_double_quotes(&add_single_quotes(text)) // Error! "returns a value referencing data owned by the current function"
}

fn add_double_quotes(text: &str) -> Cow<str> {
    DOUBLE_QUOTED_TEXT.replace_all(text, "“$content”")
}

fn add_single_quotes(text: &str) -> Cow<str> {
    SINGLE_QUOTE.replace_all(text, "’")
}


#[cfg(test)]
mod tests {
    use crate::{add_typography};

    #[test]
    fn converts_to_double_quotes() {
        assert_eq!(add_typography(r#""Hello""#), "“Hello”");
    }

    #[test]
    fn converts_a_single_quote() {
        assert_eq!(add_typography("Today's Menu"), "Today’s Menu");
    }
}

这是我能想到的最好的方法,但是当链接三个或四个函数时,这会很快变得丑陋:

fn add_typography(input: &str) -> Cow<str> {
    match add_single_quotes(input) {
        Cow::Owned(output) => add_double_quotes(&output).into_owned().into(),
        _                  => add_double_quotes(input),
    }
}

A Cow 包含 maybe-owned 数据。

我们可以从 replace_all 函数的作用推断它 return 只有在替换没有发生的情况下才借用数据,否则它必须 return 新的、拥有的数据。

当内部调用进行替换而外部调用没有进行替换时,就会出现问题。在这种情况下,外部调用将简单地将其输入作为 Cow::Borrowed 传递,但它借用内部调用 return 的 Cow::Owned 值,其数据现在属于 Cow add_typography() 本地的临时文件。因此,该函数将 return 一个 Cow::Borrowed,但会从临时借用,这显然不是 memory-safe。

基本上,此函数只会在任一调用均未进行替换时 return 借用数据。我们需要的是一个助手,它可以在 returned Cow 本身拥有时通过调用层传播 owned-ness。

我们可以在 Cow 之上构造一个 .map() 扩展方法,它正是这样做的:

use std::borrow::{Borrow, Cow};

trait CowMapExt<'a, B>
    where B: 'a + ToOwned + ?Sized
{
    fn map<F>(self, f: F) -> Self
        where F: for <'b> FnOnce(&'b B) -> Cow<'b, B>;
}

impl<'a, B> CowMapExt<'a, B> for Cow<'a, B>
    where B: 'a + ToOwned + ?Sized
{
    fn map<F>(self, f: F) -> Self
        where F: for <'b> FnOnce(&'b B) -> Cow<'b, B>
    {
        match self {
            Cow::Borrowed(v) => f(v),
            Cow::Owned(v) => Cow::Owned(f(v.borrow()).into_owned()),
        }
    }
}

现在您的呼叫站点可以保持干净整洁:

fn add_typography(text: &str) -> Cow<str> {
    add_single_quotes(text).map(add_double_quotes)
}