I couldn't find any prior discussion of this feature. Here's a playground that has this implemented with an extension trait.
Essentially, I'd like to add a Cow::map
function that looks a little like this:
fn map<S: ToOwned + ?Sized>(self, f: impl for<'b> FnOnce(&'b T) -> Cow<'b, S>) -> Cow<'a, S>
where
T::Owned: AsRef<T>,
{
match self {
Cow::Borrowed(borrowed) => f(borrowed),
Cow::Owned(owned) => {
let result = f(owned.as_ref());
Cow::Owned(S::to_owned(&result))
}
}
}
(I haven't hashed out the exact trait bounds, but hopefully this is Pretty Close).
It would be used something like this:
REGEX_1.replace(s, "A").map(|s| REGEX_2.replace(s, "B"))
where Regex::replace
returns a Cow<'_, str>
.
In the case where self
is already owned, this maps to another owned Cow
. However, in the case where self
is borrowed, this simply returns the result of f
, allowing for efficiently calling many Cow
-returning operations in a row.
Motivation
I had a string that I wanted to apply two regex substitutions to. If I was just doing a single replace
call, it would be fine:
fn replace_once(s: &str) -> Cow<'_, str> {
REGEX_1.replace(s, "A")
}
No issue here. The trouble comes when I want to add a second .replace
naively:
fn replace_twice(s: &str) -> Cow<'_, str> {
let temp = REGEX_1.replace(s, "A");
REGEX_2.replace(&temp, "B") // error, referencing temp, which is dropped
}
This doesn't compile, since the return value potentially references the owned string allocated by the first replace
, which is dropped after the function returns.
A trivial solution is to make this function return a String
, but that seemed unsatisfactory to me. In my specific use case, the substitution only happened very rarely, so ideally I could just return the reference to the string passed in.
Instead, I wrote this extension trait in the playground, and have found it useful for this case.
There is some prior art here, in particular, this is a fairly standard monadic operation, comparable to Result::map
(Borrowed
is comparable to Ok
, Owned
is comparable to Err
) and Option::map
. The code for a fully generic version is also somewhat tricky (has HRTB and quite a lengthy trait/function signature), which means many users will "just allocate here", when it's not really necessary. In my codebase, there were many similar snippets when the author did in fact just make the function return a String
, which is unsatisfying.
One of the things I love about Rust is the way it provides safe and ergonomic abstractions that allow me to avoid allocations, without feeling like I'm jumping through hoops. IMO, this function fits with that philosophy.
There are, of course, reasons not to include such a function in std:
- it increases maintenance burden
- it's a somewhat niche operation
- potentially other reasons I'm not aware of?
Though, since I'm writing this, my feeling ATM is that the benefits outweigh the cost. What are others' opinions? Does this seem like something that would be worth having in std? If so I'd be happy to work on a PR or RFC (I'm not sure if this is a significant enough change to need an RFC, my guess is no? I don't know what the "rules" are on this, just an impression I get by skimming the RFC book).
Thanks