Bienvenidos sean a este post, hoy hablaremos sobre la primera de tres particularidades del lenguaje.
Si bien todavia no nos hemos cruzado con muchos inconvenientes a la hora de compilarlo este lenguaje es usualmente denominado como un lenguaje memory-safe (memoria segura), dado que siempre revisara como actuara en la memoria antes de compilarlo para evitar que algo se salga de los carriles por donde deberia funcionar, para ello el lenguaje utiliza tres claves:
- Propiedades (ownership), del cual hablaremos aqui
- Referencias o prestamos (borrowing)
- Tiempo de vida de la aplicacion (application lifetime)
Antes de comenzar con el tema en este post debemos hablar sobre un tema denominado abstraccion.
Como dijimos una particularidad que destaca este lenguaje con respecto a otros es que se denomina memory-safe lo cual deriva en un ideal llamado abstraccion de costo cero (zero cost abstraction), dado que una abstraccion nos permite elevar una construccion de bajo nivel mas alto, haciendola mas facil, segura y confiable, aunque las abstracciones pueden generar algunos inconvenientes como por ejemplo que el codigo corra mas lento o utilice mas memoria de la necesaria para ejecuciones a bajo nivel, pero en Rust esto no sucede porque todo esto es realizado generalmente en el momento de la compilacion, dado que el compilador crea las abstracciones y las ejecuta, ocasionando que el compilador genere el mejor codigo posible pero esto tiene un inconveniente: el compilador puede rechazar un codigo que consideremos correcto dado que nunca pensaremos igual que el compilador pero con el tiempo y practica nos iremos adaptando a este y podremos trabajar con minimos rechazos 😅
Con esto comentado podemos comenzar con el ownership o propiedad, para ello vamos a ver el siguiente codigo:
fn funcion()
{
let a = 1.32f32;
}
Cuando llamemos a esta funcion esta entrara dentro de un scope o rango, esto hara que el compilador ubique en memoria el valor de la variable, mas exactamente en el stack, pero una vez que terminemos con la funcion esta se ira del rango por lo tanto el lenguaje se encargara automaticamente de liberar el espacio de memoria donde estaba el valor de la variable, si en lugar de una variable comun usaramos un vector o array este se ubicaria en la parte de heap de la memoria, y cuando finalice la funcion que lo ubico en memoria este limpiara la informacion y todas las referencias que existan a este, si bien sobre esto ya hablamos en otros posts estos dos ejemplos nos sirven para entender que las variables se manejan de manera distinta dependiendo del tipo, vamos a considerar la declaracion de la siguiente variable:
let a = 1.32f32;
Esta es la union mas simple que podemos hacer en el lenguaje porque une un valor a una variable y despues podemos recuperarlo a traves de este, y esto es muy adecuado para la propiedad porque como regla Rust solo puede tener algo unido solo una vez, vamos a considerar el siguiente ejemplo:
let mivec = vec![1i16, 2i16, 3i16];
let otrovec = mivec;
Esto para nosotros tiene toda la logica porque primero creamos una variable de tipo vector con sus respectivos valores, luego creamos otra variable y le asignamos la variable anterior, siguiendo la logica de siempre este hara una copia de la variable en la nueva uniendo a ambas pero esto fallara porque le estamos diciendo que ahora la propiedad de mivec pertenece a la nueva variable por lo tanto esta queda fuera del rango, se elimina y ya no podra ser utilizada y esto hara que el compilador evite su ejecucion, pero esto tambien nos puede suceder con una funcion para ello vamos a crear un ejemplo.
Crearemos un nuevo proyecto que llamaremos prop, una vez creado iremos al archivo main.rs y modificaremos el codigo generado automaticamente con el siguiente:
main.rs
fn vec_transfer(v: Vec<i32>)
{
println!("v[0] en la funcion = {}", v[0]);
}
fn main()
{
let mivec = vec![1i32, 2i32, 3i32];
vec_transfer(mivec);
println!("mivec[0] es: {}", mivec[0]);
}
Este es un ejemplo simple donde tenemos una funcion que recibe a un valor de tipo vector y simplemente mostraremos la primera posicion del argumento recibido, luego en el main crearemos un vector, lo pasamos a la funcion anterior y luego mostramos en pantalla, hasta aqui un codigo bien simple y que deberia funcionar o no?, compilemos y veamos:
tinchicus@dbn001vrt:~/lenguajes/rust/prop$ cargo build
Compiling prop v0.1.0 (/home/tinchicus/lenguajes/rust/prop)
error[E0382]: borrow of moved value: `mivec`
--> src/main.rs:10:30
|
8 | let mivec = vec![1i32, 2i32, 3i32];
| ----- move occurs because `mivec` has type `Vec<i32>`, which does not implement the `Copy` trait
9 | vec_transfer(mivec);
| ----- value moved here
10 | println!("mivec[0] es: {}", mivec[0]);
| ^^^^^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `prop` due to previous error
tinchicus@dbn001vrt:~/lenguajes/rust/prop$
Aqui se aplica todo lo que hablamos, cuando pasamos un vector a una funcion estamos haciendo una transferencia similar a la que hablamos anteriormente por lo tanto queda fuera del rango y al querer usarlo obtendremos el error y no se compilara pero para entenderlo vamos a analizarlo desde su forma mas basica como es la memoria, veamos la siguiente linea:
let mivar = 32i32;
Tendemos a pensar que esta declaracion es una variable que almacena un valor de tipo i32 en la variable pero Rust lo ve de manera diferente, porque primero crea el espacio en memoria para almacenar un valor de i32, luego asigna el valor para finalmente hacer la union de este espacio de memoria con la variable y como podran deducir todo lo opuesto a como lo razonamos, con la variable anterior hagamos lo siguiente:
let vardos = mivar;
Como dijimos anteriormente nosotros al ver esto pensamos que en vardos guardaremos el valor de mivar pero en realidad Rust mueve la union de donde esta el valor de mivar a su nuevo propietario, en este caso vardos, y una conducta que no mencionamos de Rust es que no permite que la informacion este unida a dos diferentes objetos, por lo tanto una vez transferido se procede a eliminar a myvar ocasionando el error que vimos anteriormente, esto mismo ocurre con el heap (lo hablado recien fue en el stack), con la diferencia de que en lugar de tener un solo elemento tendremos tres pero basicamente es lo mismo porque se seguira respetando la regla de Rust sobre que dos objetos no pueden apuntar a una misma informacion, entonces podemos ir finalizando diciendo que:
La propiedad u ownership es la union que existe entre la variable y el espacio de memoria que utilizamos para almacenar un valor y este no puede ser compartido
El tinchicus
Seguramente se preguntaran como podremos hacer para obtener una conducta similar a otros lenguajes, bueno eso es un tema que veremos en el proximo post.
En resumen, hoy hemos visto que es propiedad o ownership, como es, como nos afecta, como el compilador trabaja con este tema, evitando probables errores pero tambien el inconveniente que nos puede crear y evitando que se compile, hemos analizado un poco mas el tema y por ultimo una simple reflexion de como es la propiedad, 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
