Oto nadmiernie mądry makro rozwiązanie:
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);
}
Podstawowym założeniem jest to, że możemy wziąć krotki i rozpakować go do &[&fmt::Display]
. Kiedy już to zrobimy, możemy bezpośrednio zamapować każdy element na ciąg znaków, a następnie połączyć je wszystkie za pomocą separatora. Oto, co to będzie wyglądać na własną rękę:
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);
}
Następnym krokiem byłoby stworzenie cechę i wdrożyć go w konkretnym przypadku:
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(", "));
}
ten realizuje jedynie naszą cechę dla określonej wielkości krotki, która może być w porządku w niektórych przypadkach, ale na pewno nie jest jeszcze dostępna fajna. Model standard library wykorzystuje niektóre makra, aby zmniejszyć uciążliwość kopiowania i wklejania, które trzeba wykonać, aby uzyskać więcej rozmiarów. Zdecydowałem się na jeszcze lżejszą i zmniejszyć kopiowanie i wklejanie tego rozwiązania!
Zamiast wyraźnie i jawnie wymieniać każdy rozmiar krotki i odpowiadającą jej nazwę indeksu/nazwy, zrobiłem moje makro rekursywne. W ten sposób muszę go tylko raz wyliczyć, a wszystkie mniejsze rozmiary są tylko częścią połączenia rekursywnego. Niestety, nie mogłem wymyślić, jak zrobić to w kierunku do przodu, więc po prostu odwróciłem wszystko i cofnąłem się. Oznacza to niewielką nieefektywność, ponieważ musimy używać odwrotnego iteratora, ale ogólnie rzecz biorąc powinna to być niewielka cena do zapłacenia.
Należy zauważyć, że w Rust arity krotki jest znany w czasie kompilacji (w przeciwieństwie do Pythona), a Rust nie ma jeszcze * parametrów variadycznych *; krotki są kompilowane przez kompilator i cechy są zaimplementowane dla wielu arach "ręcznie". –
Python ma tendencję do typowania glom razem, gdy Rust ma przeciwną tendencję; w Pythonie wszystkie krotki są tego samego typu i wszystkie funkcje tego samego typu; w Rust, każda kombinacja typów pól w krotce jest innego typu, a każda funkcja jest unikalnym typem. Jest to różnica w podejściu: w Pythonie wszystko jest rozwiązywane w czasie wykonywania; w Rust, w czasie kompilacji. Krotki są w Rust po prostu nienazwanymi strukturami krotek, bez żadnego związku ze sobą. –
@MatthieuM .: Czy możliwe byłoby uzyskanie arytmu krotki jako stałej? – oleid