这里有一个过于聪明的宏解决方案:
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);
}
下一步是创建一个trait并为特定情况实现它:
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(", "));
}
这只为特定大小的元组实现了我们的特质,对于某些情况来说可能是可以接受的,但肯定还不够酷。 标准库 使用一些宏来减少重复复制和粘贴的繁琐工作,以获取更多的大小。我决定变得更懒,并减少那个解决方案的复制和粘贴!
我没有清晰而明确地列出每个元组大小及相应的索引/泛型名称,而是让我的宏递归。这样,我只需要列出一次,所有较小的大小都只是递归调用的一部分。不幸的是,我无法想出如何使其向前进行,所以我将一切都颠倒过来并向后进行。这意味着存在一个小小的低效性,因为我们必须使用反向迭代器,但总体上应该是支付的小代价。
fmt::Display
(这就是.to_string()
使用的内容)中,你会期望什么呢?a b c
、a, b, c
、(a, b, c)
?它们都不是“正确”的,也没有一个是“正确”的。 - Chris Morgan