Perhaps my terminology is wrong, but is there a way to use postfix notation to call a function in Rust without defining a new trait? Basically, I have a vector of &str
and I'd like to convert them into a string with the notation myvec.as_string()
. Currently, I can do this with the code
trait Foo {
fn as_string(&self) -> String;
}
impl Foo for Vec<&str> {
fn as_string(&self) -> String {
let mut mystr = self
.iter()
.fold(String::new(),|sum,s| format!("{}{}:", sum, s));
mystr.pop();
mystr
}
}
fn main() {
let my_vec = vec!["bar", "buz", "baz"];
use crate::Foo;
println!("{}", my_vec.as_string());
}
That said, in order to make this work, I needed to define a trait called Foo
that I don't really care about and the trait needed to be opened with use crate::Foo
prior to the call to as_string
. Is there a better way to accomplish this? And, to be clear, I'd like to avoid the notation as_string(myvec)
if possible because the postfix notation has been nice for chaining together commands.
This is a common pattern!
If you want to add methods to a type that is defined in another crate, the official way to do so is it define a trait and implement it for that type. If the type is from another crate then this is the only way to do it.
A ubiquitous example of this is the crate itertools
which uses a trait to add useful methods to every existing implementation of std::iter::Iterator
.
Itertools
works just as you describe. There is a trait which declares a number of methods:
pub trait Itertools : Iterator {
fn interleave<J>(self, other: J) -> Interleave<Self, J::IntoIter>
where J: IntoIterator<Item = Self::Item>,
Self: Sized
{
interleave(self, other)
}
// etc...
}
It is defined for all Iterator
s:
impl<T: ?Sized> Itertools for T where T: Iterator { }
And, whenever you want to use these extra methods, you import it:
use itertools::Itertools;
let it = (1..7).interleave(vec![-1, -2]);
itertools::assert_equal(it, vec![1, -1, 2, -2, 3, 4, 5, 6]);