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 ![]()