Go znam stosunkowo dobrze, ponieważ napisałem w nim wiele małych programów. Rdza, oczywiście, jestem mniej obeznana, ale pilnuje.
Po niedawnym przeczytaniu http://yager.io/programming/go.html pomyślałem, że osobiście zbadam dwa sposoby postępowania z lekami generycznymi, ponieważ artykuł wydawał się niesłusznie krytykować Go, gdy w praktyce niewiele było interfejsów nie udało się osiągnąć elegancko. Ciągle słyszałem szum o tym, jak potężne były Cechy Rdza i tylko krytykę ludzi na temat Go. Mając pewne doświadczenie w Go, zastanawiałem się, jak to była prawda i jakie były ostatecznie różnice. Odkryłem, że cechy i interfejsy są bardzo podobne! Ostatecznie nie jestem pewien, czy coś mi umknęło, więc oto krótki przegląd ich podobieństw, abyś mógł mi powiedzieć, co przegapiłem!
Teraz spójrzmy na interfejsy Go z ich dokumentacji :
Interfejsy w Go umożliwiają sposób określania zachowania obiektu: jeśli coś może to zrobić, można go tutaj użyć.
Zdecydowanie najczęściej spotykanym interfejsem jest Stringer
ciąg znaków reprezentujący obiekt.
type Stringer interface {
String() string
}
Tak więc każdy obiekt, który się String()
na nim zdefiniował, jest Stringer
obiektem. Można tego użyć w podpisach typu, które func (s Stringer) print()
pobierają prawie wszystkie obiekty i drukują je.
Mamy również, interface{}
który bierze dowolny przedmiot. Następnie musimy określić typ w czasie wykonywania przez odbicie.
Teraz spójrzmy na cechy rdzy z ich dokumentacji :
Najprościej mówiąc, cechą jest zbiór zer lub więcej sygnatur metod. Na przykład, moglibyśmy zadeklarować cechę Printable dla rzeczy, które można wydrukować na konsoli, za pomocą jednej sygnatury metody:
trait Printable {
fn print(&self);
}
To natychmiast wygląda całkiem podobnie do naszych interfejsów Go. Jedyną różnicą, którą widzę, jest to, że definiujemy „Implementacje” Cech, a nie tylko definiujemy metody. Tak robimy
impl Printable for int {
fn print(&self) { println!("{}", *self) }
}
zamiast
fn print(a: int) { ... }
Pytanie dodatkowe: Co dzieje się w Rust, jeśli zdefiniujesz funkcję, która implementuje cechę, ale nie używasz impl
? To po prostu nie działa?
W przeciwieństwie do interfejsów Go, system typów Rust'a ma parametry typu, które pozwalają ci robić właściwe ogólne i takie rzeczy, jak interface{}
podczas gdy kompilator i środowisko wykonawcze faktycznie znają typ. Na przykład,
trait Seq<T> {
fn length(&self) -> uint;
}
działa na dowolnym typie, a kompilator wie, że typ elementów Sekwencji w czasie kompilacji zamiast używać odbicia.
Teraz pytanie: czy brakuje mi tutaj jakichkolwiek różnic? Są one naprawdę , że podobna? Czy nie brakuje mi bardziej fundamentalnej różnicy? (W użyciu. Szczegóły implementacji są interesujące, ale ostatecznie nie są ważne, jeśli działają tak samo.)
Oprócz różnic składniowych, rzeczywiste różnice, które widzę, to:
- Go ma automatyczną metodę wysyłania metod vs. Rust wymaga (?)
impl
S do implementacji cechy- Elegancki kontra wyraźny
- Rdza ma parametry typu, które pozwalają na prawidłowe generyczne bez odbicia.
- Go naprawdę nie ma tutaj odpowiedzi. Jest to jedyna rzecz, która jest znacznie bardziej wydajna i ostatecznie zastępuje metody kopiowania i wklejania z różnymi typami podpisów.
Czy to jedyne nietrywialne różnice? Jeśli tak, wydaje się, że system interfejsu / typu Go nie jest w praktyce tak słaby, jak się spostrzega.
AnyMap
jest dobrym pokazem mocnych stron Rdzy, łącząc obiekty cech z generycznymi, aby zapewnić bezpieczną i ekspresyjną abstrakcję delikatnej rzeczy, którą w Go z konieczności trzeba by napisaćmap[string]interface{}
.