Generyczne tablice w Ruście


Postanowiłem ostatnio w ramach ćwiczenia "przetłumaczyć" symulator czarnej dziury na Rusta. Na początek zdecydowałem się stworzyć bibliotekę obsługującą geometrię różniczkową (rachunek tensorowy, metrykę itp.). Natknąłem się przy tym dość szybko na pewien problem.

Rust i tablice

Tensory są obiektami mającymi pewną ustaloną liczbę współrzędnych, zależną od wymiaru przestrzeni, na której funkcjonują. Np. w przestrzeni n-wymiarowej wektory mają n współrzędnych, tensory rzędu 2 (np. metryka) mają ich n^2 itp. Do reprezentacji takich obiektów idealne są tablice.
Rust radzi sobie z tablicami bez problemu. N-elementową tablicę z elementami typu T zapisuje się w Ruście jako [T; N]. I wszystko byłoby fajnie, gdyby nie drobny szczegół - chciałbym, żeby mój kod był niezależny od wymiaru przestrzeni.

Problem

Rust posiada możliwość tworzenia tzw. typów generycznych. Są to typy danych, w których możemy określić, jakiego typu mają być ich wewnętrzne szczegóły. Np., moglibyśmy łatwo stworzyć generyczny wektor 3-wymiarowy:

Co jednak, gdybyśmy chcieli stworzyć wektor n-wymiarowy?

Nic z tego. Nie ma w Ruście sposobu na wyrażenie tego, żeby parametrem typu była długość tablicy.
Sprawa wydawała się beznadziejna, ale przypadkiem natknąłem się na kod, który zasugerował, że rozwiązanie może istnieć.

dimensioned i typy Peano

W bibliotece dimensioned autor zaimplementował arytmetykę liczb naturalnych... na typach danych. Podstawa wygląda tak:
Po pierwsze, definiujemy zero:

Po drugie, określamy, że zero jest liczbą naturalną:

Po trzecie, definiujemy następnik liczby naturalnej:

Po czwarte: następnik liczby naturalnej też jest liczbą naturalną

I to w zasadzie tyle. Jeśli chcemy, możemy jeszcze ponazywać typy:

Może się jeszcze przydać zdefiniowanie funkcji, która zwraca faktyczną liczbę:

Tym sposobem zdefiniowaliśmy typy danych odpowiadające liczbom naturalnym. Są to już prawdziwe typy - więc można ich używać jako parametrów w typach generycznych. Niestety, [T; Two] nadal nie zadziała.

Możemy zrobić jednak coś takiego: dla typu oznaczającego zero zdefiniować pustą strukturę, a dla każdego następnika strukturę z dwoma polami - strukturą poprzednika i jedną wartością typu T. W ten sposób struktura zdefiniowana dla typu odpowiadającego np. liczbie 5 będzie miała w sobie miejsce na 5 elementów typu T - a więc można będzie ją traktować jak 5-elementową tablicę! Do roboty.

Po pierwsze, musimy jakoś określić strukturę stowarzyszoną z danym typem:

To teraz struktury dla zera i następników:

Mamy zdefiniowane tablice stowarzyszone z typami liczbowymi, co już w zasadzie by wystarczyło. Fajnie jednak będzie mieć składnię podobną do [T; N]:

I w tym momencie GenericArray już zadziała. Moglibyśmy zdefiniować również Vector>. Mamy prawdziwe generyczne tablice :)

Kolejny problem

Okazuje się, że taki system ma jedno poważne ograniczenie. Typy liczbowe są zdefiniowane rekurencyjnie i bardzo szybko osiągają limit systemu typów kompilatora - a mianowicie, w okolicach liczby 64. Można co prawda ten limit zwiększyć, ale nadal nawet nie do wielkości rzędu 1000, poza tym jest to duże obciążenie dla kompilatora. Należałoby to jakoś zmienić.

W wątku na reddicie użytkownik llogiq zasugerował zastosowanie systemu binarnego, podobnego do tego z biblioteki shoggoth.rs. Po przyjrzeniu się temu sposobowi, postanowiłem go spróbować.

Początek jest podobny, z tym że definiujemy nie jeden, a dwa typy:

Następnie, zamiast następników, definiujemy liczby binarne:

W tym momencie jako liczby naturalne mamy _0, _1, (_1, _0), (_1, _1), ((_1, _0), _0), ... - czyli zaimplementowaliśmy system binarny! Oczywiście, przyda się znowu konwersja na liczbę całkowitą:

Dalszy ciąg jest podobny. Zeru przypisujemy pustą strukturę, jedynce - strukturę z jednym polem. Następnie każdej parze (N, _0) strukturę z dwoma polami typu N::Array, a parze (N, _1) - z dwoma polami N::Array i jednym dodatkowym elementem. I tak oto mamy generyczne tablice opisywane binarnie.

Faktyczna implementacja w repozytorium nieco różni się od tej przedstawionej tutaj (głównie nazwami struktur) - zapraszam do przeglądania! :)