下面是一个非常聪明的宏解决方案:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {};
( ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )* ) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where
$typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts: &[&::std::fmt::Display] = &[&self.$idx, $( &self.$nidx ),*];
parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().join(sep)
}
}
tuple_impls!($( ($nidx => $ntyp), )*);
};
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
fn main() {
let a = (1.3, 1, 'c');
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}
基本思想是我们可以把一个元组解压成一个&[&fmt::Display]
.一旦我们有了它,就可以直接将每个项目映射成一个字符串,然后用分隔符将它们组合起来.下面是它本身的样子:
fn main() {
let tup = (1.3, 1, 'c');
let slice: &[&::std::fmt::Display] = &[&tup.0, &tup.1, &tup.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
let joined = parts.join(", ");
println!("{}", joined);
}
下一步是创建一个特征,并针对具体 case 实施:
trait TupleJoin {
fn tuple_join(&self, sep: &str) -> String;
}
impl<A, B, C> TupleJoin for (A, B, C)
where
A: ::std::fmt::Display,
B: ::std::fmt::Display,
C: ::std::fmt::Display,
{
fn tuple_join(&self, sep: &str) -> String {
let slice: &[&::std::fmt::Display] = &[&self.0, &self.1, &self.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
parts.join(sep)
}
}
fn main() {
let tup = (1.3, 1, 'c');
println!("{}", tup.tuple_join(", "));
}
这只在特定大小的元组中实现了我们的特性,这在某些情况下可能是好的,但肯定还不是cool.standard library使用了一些宏来减少复制和粘贴的繁重工作,这是获得更多尺寸所需的.我决定even lazier岁,并减少该解决方案的复制和粘贴!
我没有清楚明确地列出元组的每个大小以及相应的索引/泛型名称,而是让宏递归.这样,我只需要列出一次,所有较小的大小只是递归调用的一部分.不幸的是,我不知道如何使它朝着一个前进的方向前进,所以我只是把所有的东西翻转过来,然后向后走.这意味着我们必须使用反向迭代器,这有一点效率低下,但总的来说,这应该是一个很小的代价.