Estoy relativamente familiarizado con Go, habiendo escrito una serie de pequeños programas en él. Rust, por supuesto, estoy menos familiarizado pero vigilando.
Después de leer recientemente http://yager.io/programming/go.html , pensé en examinar personalmente las dos formas en que se manejan los genéricos porque el artículo parecía criticar injustamente a Go cuando, en la práctica, no había mucho que Interfaces no pudo lograr con elegancia. Seguía escuchando la exageración sobre cuán poderosos eran los Rasgos de Rust y nada más que las críticas de la gente sobre Go. Teniendo algo de experiencia en Go, me preguntaba qué tan cierto era y cuáles eran las diferencias en última instancia. ¡Lo que encontré fue que los rasgos e interfaces son bastante similares! En última instancia, no estoy seguro de si me estoy perdiendo algo, así que aquí hay un resumen educativo rápido de sus similitudes para que pueda decirme lo que me perdí.
Ahora, echemos un vistazo a Go Interfaces desde su documentación :
Las interfaces en Go proporcionan una forma de especificar el comportamiento de un objeto: si algo puede hacer esto, entonces puede usarse aquí.
Con mucho, la interfaz más común es la Stringer
que devuelve una cadena que representa el objeto.
type Stringer interface {
String() string
}
Entonces, cualquier objeto que se haya String()
definido en él es un Stringer
objeto. Esto se puede usar en firmas de tipo tal que func (s Stringer) print()
tome casi todos los objetos y los imprima.
También tenemos el interface{}
que lleva cualquier objeto. Luego debemos determinar el tipo en tiempo de ejecución a través de la reflexión.
Ahora, echemos un vistazo a Rust Traits de su documentación :
En su forma más simple, un rasgo es un conjunto de cero o más firmas de método. Por ejemplo, podríamos declarar el rasgo Imprimible para cosas que se pueden imprimir en la consola, con una firma de método único:
trait Printable {
fn print(&self);
}
Esto inmediatamente se parece bastante a nuestras interfaces Go. La única diferencia que veo es que definimos 'Implementaciones' de Rasgos en lugar de solo definir los métodos. Entonces lo hacemos
impl Printable for int {
fn print(&self) { println!("{}", *self) }
}
en lugar de
fn print(a: int) { ... }
Pregunta adicional: ¿Qué sucede en Rust si define una función que implementa un rasgo pero no la usa impl
? ¿Simplemente no funciona?
A diferencia de las interfaces de Go, el sistema de tipos de Rust tiene parámetros de tipo que le permiten hacer genéricos adecuados y cosas como interface{}
mientras el compilador y el tiempo de ejecución realmente conocen el tipo. Por ejemplo,
trait Seq<T> {
fn length(&self) -> uint;
}
funciona en cualquier tipo y el compilador sabe que el tipo de los elementos de secuencia en tiempo de compilación en lugar de usar la reflexión.
Ahora, la pregunta real: ¿me estoy perdiendo alguna diferencia aquí? ¿Son realmente tan similares? ¿No hay alguna diferencia más fundamental que me estoy perdiendo aquí? (En uso. Los detalles de implementación son interesantes, pero en última instancia no son importantes si funcionan de la misma manera).
Además de las diferencias sintácticas, las diferencias reales que veo son:
- Go tiene un envío automático de métodos vs. Rust requiere (?)
impl
S para implementar un Rasgo- Elegante vs explícito
- Rust tiene parámetros de tipo que permiten genéricos adecuados sin reflexión.
- Ir realmente no tiene respuesta aquí. Esto es lo único que es significativamente más poderoso y, en última instancia, es solo un reemplazo para copiar y pegar métodos con diferentes tipos de firmas.
¿Son estas las únicas diferencias no triviales? Si es así, parecería que el sistema de interfaz / tipo de Go, en la práctica, no es tan débil como se percibe.
AnyMap
es una buena demostración de las fortalezas de Rust, combinando objetos de rasgos con genéricos para proporcionar una abstracción segura y expresiva de lo frágil que necesariamente se escribiría en Gomap[string]interface{}
.interface{}
. Si bien Rust parece técnicamente superior, sigo pensando que las críticas a Go ... han sido demasiado duras. El poder del programador está más o menos a la par para el 99% de las tareas.