Вот чрезмерно умный макрос решение:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
// FIXME(#19630) Remove this work-around
macro_rules! e {
($e:expr) => { $e }
}
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] = e!(&[&self.$idx, $(&self.$nidx),*]);
parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().connect(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.connect(", ");
println!("{}", joined);
}
Следующим шагом будет создание черта и реализовать его для конкретного случая:
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.connect(sep)
}
}
fn main() {
let tup = (1.3, 1, 'c');
println!("{}", tup.tuple_join(", "));
}
Это только реализует нашу черту для определенного размера из кортежа, что может быть хорошо для некоторых случаев, но, конечно же, не прохладно еще. standard library использует некоторые макросы, чтобы уменьшить тяжелую работу копирования и вставки, которую вам нужно будет сделать, чтобы получить больше размеров. Я решил быть даже ленивым и уменьшить копию и вставить это решение!
Вместо явного и явного перечисления каждого размера кортежа и соответствующего имени индекса/родословной, я сделал свой макрорекурсивный. Таким образом, мне нужно только перечислить его один раз, а все меньшие размеры - это всего лишь часть рекурсивного вызова. К сожалению, я не мог понять, как сделать это в направлении вперед, поэтому я просто перевернул все вокруг и пошел назад. Это означает, что есть небольшая неэффективность в том, что мы должны использовать обратный итератор, но в целом это будет небольшая цена для оплаты.
Обратите внимание, что в Русте арностью кортежа известно во время компиляции (в отличие от Python), и никакой Rust не имеет * вариационных параметров *; Кортежи задаются специальным образом с помощью компилятора, и черты реализуются для нескольких атрибутов «вручную». –
Python имеет тенденцию к слиянию типов, где Rust имеет противоположную тенденцию; в Python все кортежи имеют один тип и все функции одного типа; в Rust каждая комбинация типов полей в кортеже - это другой тип, и каждая функция представляет собой свой собственный уникальный тип. Это разница в подходе: в Python все разрешено во время выполнения; в Rust, во время компиляции. Кортежи в Rust просто неназванные кортежные структуры, не имеющие отношения друг к другу. –
@MatthieuM: Можно ли получить атрибут кортежа как постоянный? – oleid