Bienvenidos sean a este post, hoy hablaremos sobre un concepto heredado de C++.
Como dijimos este es el mismo concepto que se aplica en C++ o C# donde como su nombre lo indica representa a un tipo generico y ninguno en particular, al igual que en los lenguajes anteriores se lo representa con la letra T pero esto no es obligatorio, y en este lenguaje tambien tenemos una regla muy simple para utilizarlo:
Los tipos deben coincidir
El tinchicus
Es decir si al generico le definimos un tipo i32 no le podemos pasar un valor de tipo f32 o string, esto hara que el compilador falle, veamos un ejemplo de uso de genericos:
enum Respuesta<S, N>
{
Ok(S),
Err(N),
}
En este caso los genericos no tienen que ser del mismo tipo, podemos asignar a S un tipo String y a N un tipo bool, mientras despues respetemos los tipos que asignamos todo funcionara correctamente, veamos un ejemplo de como ponerlo en practica:
enum Opciones<T>
{
tipo_ejemplo(T),
None
}
let variable: Opciones<i32> = tipo_ejemplo(236);
Como pueden ver creamos un enum generico donde tenemos una variable llamada tipo_ejemplo que recibira el tipo generico, despues cuando creamos una variable de este enum pasamos el nombre del enum y que tipo de dato sera T, luego pasamos la propiedad con el tipo que debe coincidir con el definido anteriormente, pero tambien disponemos de funciones genericas para entenderlo vamos a ver primero una funcion normal:
fn miFuncion(x: i32)
{
... instrucciones ...
}
Esta funcion solo puede recibir los datos de tipo i32 cualquier otro sera rechazado y como conclusion podemos decir que solo puede procesar este tipo de datos, pasemos a ver la misma funcion pero generica:
fn miFuncion<T>(x: T)
{
... instrucciones ...
}
Esta funcion en cambio puede procesar cualquier clase de tipo porque cada vez que la llamemos al informar el tipo para el generico nos permitira trabajarlo a nuestra necesidad sin necesidad de tener una funcion para manejar cada tipo que necesitemos, tambien podemos usarlo de varias formas:
fn miFuncion<T>(x: T, y: T) { ... }
El tipo generico podemos usarlo en todos los argumentos que poseamos, veamos otro:
fn miFuncion<T, U, V>(a: T, b: U, c: V) { ... }
Podemos tener varios tipos genericos separados por comas, luego podemos asignarlos a los distintos argumento y por ultimo:
fn miFuncion<T>(a: T, b: i32) -> T { ... }
Tambien podemos devolver el valor del tipo generico que asignemos, con todo esto comentado vamos a crear un ejemplo para verlo en accion.
Primero crearemos al ejemplo que llamaremos generico, una vez creado iremos al archivo main.rs y modificaremos el codigo generado con el siguiente:
main.rs
fn multiplicar<T>(a: T, b: T) -> T
{
return a * b;
}
fn main()
{
let v1=multiplicar(123, 456);
let v2=multiplicar(3.14, 6.28);
println!("v1 = {}", v1);
println!("v2 = {}", v2);
}
Primero tendremos una funcion generica donde el valor que pasemos sera el utilizado en los argumentos y devolveremos este tipo, dentro del bloque devolveremos la multiplicacion entre los dos argumentos, lo siguiente sera el main, donde definiremos dos variables que contendran la devolucion de dos llamados a la funcion, observen como una misma funcion permite recibir dos tipos distintos pero recuerden que ambos deben ser del mismo tipo, por ultimo mostraremos los valores obtenidos, si lo compilamos veremos que nos devuelve un error y no se compila, veamos la linea mas importante:
error[E0369]: cannot multiply `T` by `T`
Esto es debido a que el compilador desconoce completamente cual es el tipo de T, por lo tanto no tiene forma de verificar que este tipo pueda ser multiplicado pero para ello tenemos una solucion y para eso debemos modificar el codigo de la siguiente manera:
main.rs
use std::ops::Mul;
fn multiplicar<T: Mul<Output = T>>(a: T, b: T) -> T
{
return a * b;
}
fn main()
{
let v1=multiplicar(123, 456);
let v2=multiplicar(3.14, 6.28);
println!("v1 = {}", v1);
println!("v2 = {}", v2);
}
Primero importaremos la clase Mul, la cual es la encargada de manejar al operador de multiplicacion (*), la siguiente modificacion es la importante:
fn multiplicar<T: Mul<Output = T>>(a: T, b: T) -> T
Aqui basicamente le estamos diciendo que todos los tipos que sean manejados por T podran ser multiplicados, siempre y cuando sean de tipo numericos porque los de tipo String fallaran, con estas dos multiplicaciones si probamos de compilarlo y ejecutarlo veremos la siguiente salida:
tinchicus@dbn001vrt:~/lenguajes/rust/refmut/generico$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/generico`
v1 = 56088
v2 = 19.7192
tinchicus@dbn001vrt:~/lenguajes/rust/refmut/generico$
Con esto ya tenemos una forma muy buena para poder manejarlo pero del codigo anterior podemos modificar la funcion de la siguiente forma para que valide la multiplicacion:
fn multiplicar<T>(a: T, b: T) -> T
where T: Mul<Output = T>
{
return a * b;
}
Es exactamente lo mismo pero de una forma mas «monona» tenganlo en cuenta porque inclusive puede ser mejor para tener un codigo mejor documentado, nuestro siguiente tema sera ver como saber cual es el tipo que estamos manejando.
Afortunadamente ahora disponemos de una funcion del tipo typeof que nos devuelve cual es el tipo de dato que estamos manejando, para ello usaremos el ejemplo anterior y lo modificaremos de la siguiente forma:
main.rs
use std::ops::Mul;
use std::any::type_name;
fn mostrar_tipo<T>(_: &T)
{
let nombre = type_name::<T>();
println!("{}", nombre);
}
fn multiplicar<U>(a: U, b: U) -> U
where U: Mul<Output = U>
{
return a * b;
}
fn main()
{
let v1=multiplicar(123, 456);
let v2=multiplicar(3.14, 6.28);
mostrar_tipo(&v1);
mostrar_tipo(&v2);
}
Vamos a analizar las modificaciones que realizamos, veamos la primera:
use std::any::type_name;
Esta sera para poder importar y utilizar a la nueva funcion, lo siguiente sera este nuevo metodo:
fn mostrar_tipo<T>(_: &T)
{
let nombre = type_name::<T>();
println!("{}", nombre);
}
Esta es la nueva funcion, la cual manejara un tipo generico y volvemos a usar el operador _, del cual hablamos en este post, y al trabajar como un comodin nos viene ideal para poder recibir al generico, en el bloque crearemos una variable que sera la utilizada para almacenar el resultado del metodo type_name, por ultimo mostramos el valor almacenado en esta variable, despues en main seguimos definiendo dos variables con los resultados de la funcion multiplicar y luego llamamos a la funcion mostrar_tipo para cada una de las versiones antes definidas, compilemos y veamos su salida:
tinchicus@dbn001vrt:~/lenguajes/rust/generico$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/generico`
i32
f64
tinchicus@dbn001vrt:~/lenguajes/rust/generico$
Como podemos ver funciono perfectamente y con esto podemos saber cual tipo esta manejando la funcion para dejar de ser una generica.
En resumen, hoy hemos visto que es un generico, como nos beneficia con respecto a los enum, despues vimos como lo hace con las funciones, despues vimos un ejemplo y porque falla para luego poder solucionarlo, por ultimo hemos visto una funcion que nos permite identificar el tipo que esta utilizando nuestra funcion generica, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
