Estou relativamente familiarizado com o Go, tendo escrito vários pequenos programas nele. Ferrugem, é claro, estou menos familiarizada, mas fico de olho.
Tendo lido recentemente http://yager.io/programming/go.html , pensei em examinar pessoalmente as duas maneiras pelas quais os genéricos são tratados porque o artigo parecia criticar injustamente o Go quando, na prática, não havia muito o que fazer. não poderia realizar de forma elegante. Eu ficava ouvindo o hype sobre o quão poderosos os Rust's Traits eram e nada além de críticas das pessoas sobre Go. Tendo alguma experiência em Go, me perguntei como era verdade e quais eram as diferenças. O que eu descobri foi que Traços e Interfaces são bem parecidos! Por fim, não tenho certeza se estou perdendo alguma coisa, então aqui está um rápido resumo educacional de suas semelhanças para que você possa me dizer o que eu perdi!
Agora, vamos dar uma olhada no Go Interfaces na documentação :
As interfaces no Go fornecem uma maneira de especificar o comportamento de um objeto: se algo puder fazer isso, ele poderá ser usado aqui.
De longe, a interface mais comum é a Stringer
que retorna uma string que representa o objeto.
type Stringer interface {
String() string
}
Portanto, qualquer objeto que tenha String()
definido nele é um Stringer
objeto. Isso pode ser usado em assinaturas de tipo que func (s Stringer) print()
pegam quase todos os objetos e os imprimem.
Também temos interface{}
qual pega qualquer objeto. Devemos então determinar o tipo em tempo de execução através da reflexão.
Agora, vamos dar uma olhada no Rust Traits na documentação deles :
Na sua forma mais simples, uma característica é um conjunto de zero ou mais assinaturas de método. Por exemplo, poderíamos declarar a característica Printable para itens que podem ser impressos no console, com uma única assinatura de método:
trait Printable {
fn print(&self);
}
Isso imediatamente se parece bastante com as nossas interfaces Go. A única diferença que vejo é que definimos 'Implementações' de Características em vez de apenas definir os métodos. Então nós fazemos
impl Printable for int {
fn print(&self) { println!("{}", *self) }
}
ao invés de
fn print(a: int) { ... }
Pergunta bônus: O que acontece no Rust se você definir uma função que implementa uma característica, mas não a usa impl
? Isso simplesmente não funciona?
Diferentemente das Interfaces da Go, o sistema de tipos da Rust possui parâmetros de tipos que permitem fazer genéricos apropriados e coisas do tipo interface{}
enquanto o compilador e o tempo de execução realmente conhecem o tipo. Por exemplo,
trait Seq<T> {
fn length(&self) -> uint;
}
funciona em qualquer tipo e o compilador sabe que o tipo dos elementos Sequence no tempo de compilação em vez de usar reflexão.
Agora, a pergunta real: estou perdendo alguma diferença aqui? Eles são realmente assim ? Não há nenhuma diferença mais fundamental que estou perdendo aqui? (Em uso. Os detalhes da implementação são interessantes, mas no final não são importantes se funcionarem da mesma forma.)
Além das diferenças sintáticas, as diferenças reais que vejo são:
- Go possui despacho de método automático vs. Rust requer (?)
impl
S para implementar um Trait- Elegante vs. Explícito
- Rust possui parâmetros de tipo que permitem genéricos adequados sem reflexão.
- Go realmente não tem resposta aqui. Essa é a única coisa significativamente mais poderosa e, em última análise, é apenas uma substituição dos métodos de copiar e colar com diferentes assinaturas de tipo.
Essas são as únicas diferenças não triviais? Nesse caso, parece que o sistema de interface / tipo da Go não é, na prática, tão fraco quanto percebido.
AnyMap
é uma boa demonstração dos pontos fortes do Rust, combinando objetos de características com genéricos para fornecer uma abstração segura e expressiva da coisa frágil que em Go seria necessariamente escritamap[string]interface{}
.