Rust Language #
Desde Wikipedia;
Rust es un lenguaje de programacion multiparadigma, de alto nivel y de proposito general, diseñado para el rendimiento y la seguridad, especialmente la concurrencia segura. Rust es sintácticamente similar a C++, pero puede garantizar la seguridad de la memoria utilizando un verificador de préstamos para validar las referencias. Rust logra la seguridad de la memoria sin recoleccion de basura, y el conteo de referencias es opcional.
Rust fue diseñado originalmente por Graydon Hoare en Mozilla Research[18], con contribuciones de Dave Herman, Brendan Eich y otros. Los diseñadores perfeccionaron el lenguaje mientras escribían el motor de navegacion experimental Servo y el compilador de Rust. Su uso ha aumentado en la industria, y Microsoft ha experimentado con el lenguaje para componentes de software seguros y críticos.
Rust ha sido votado como el "lenguaje de programacion más querido" en la encuesta de desarrolladores de Stack Overflow cada año desde 2016, aunque solo lo utilizo el 7% de los encuestados en la encuesta de 2021.
Nota de ShyanJMC #
Si no quieres (o no puedes) instalar Rust en tu sistema por muchas reasiones que tengas, puedes usar el intérprete oficial online; https://play.rust-lang.org/
Historia #
De Wikipedia;
El lenguaje surgio de un proyecto personal iniciado en 2006 por el empleado de Mozilla Graydon Hoare, quien declaro que el proyecto posiblemente se llamaba así por la familia de hongos del oxido. Mozilla comenzo a patrocinar el proyecto en 2009 y lo anuncio en 2010. Ese mismo año, el trabajo paso del compilador inicial (escrito en OCaml) al compilador autoalojado basado en LLVM y escrito en Rust. Llamado rustc, se compilo con éxito en 2011.
La primera version prealfa numerada del compilador de Rust se produjo en enero de 2012. Rust 1.0, la primera version estable, se publico el 15 de mayo de 2015. Después de la 1.0, las versiones estables puntuales se entregan cada seis semanas, mientras que las características se desarrollan en Rust nocturno con versiones diarias, y luego se prueban con versiones beta que duran seis semanas. Cada 2 o 3 años, se produce una nueva "Edicion" de Rust. Esto es para proporcionar un punto de referencia fácil para los cambios debido a la naturaleza frecuente del calendario de lanzamiento del tren de Rust, así como para proporcionar una ventana para hacer cambios de ruptura. Las ediciones son ampliamente compatibles.
Junto con la tipificacion estática convencional, antes de la version 0.4, Rust también soportaba tipestatos. El sistema de tipado modelaba aserciones antes y después de las sentencias del programa, mediante el uso de una sentencia de comprobacion especial. Las discrepancias podían ser descubiertas en tiempo de compilacion, en lugar de en tiempo de ejecucion, como podría ser el caso de las aserciones en el codigo C o C++. El concepto de typestate no era exclusivo de Rust, ya que se introdujo por primera vez en el lenguaje NIL. Los typestates se eliminaron porque en la práctica se utilizaban poco, aunque la misma funcionalidad se puede conseguir aprovechando la semántica de movimiento de Rust.
El estilo del sistema de objetos cambio considerablemente en las versiones 0.2, 0.3 y 0.4 de Rust. La version 0.2 introdujo las clases por primera vez, y la version 0.3 añadio varias características, incluyendo destructores y polimorfismo mediante el uso de interfaces. En Rust 0.4, se añadieron los rasgos como medio para proporcionar herencia; las interfaces se unificaron con los rasgos y se eliminaron como una característica independiente. También se eliminaron las clases y se sustituyeron por una combinacion de implementaciones y tipos estructurados.
A partir de Rust 0.9 y hasta Rust 0.11, Rust tenía dos tipos de punteros incorporados: ~ y @, simplificando el modelo de memoria del núcleo. Reimplemento esos tipos de puntero en la biblioteca estándar como Box y (el ahora eliminado) Gc.
En enero de 2014, antes de la primera version estable, Rust 1.0, el editor jefe de Dr. Dobb's, Andrew Binstock, comento las posibilidades de que Rust se convirtiera en un competidor de C++ y de los otros lenguajes emergentes D, Go y Nim (entonces Nimrod). Según Binstock, aunque Rust era "ampliamente considerado como un lenguaje notablemente elegante", su adopcion se ralentizo porque cambiaba repetidamente entre versiones.
Rust tiene una interfaz de funcion extranjera (FFI) que puede ser llamada desde, por ejemplo, el lenguaje C, y puede llamar a C. Mientras que llamar a C++ ha sido historicamente un reto (desde cualquier lenguaje), Rust tiene una biblioteca, CXX, para permitir llamar a o desde C++, y "CXX tiene una sobrecarga nula o insignificante".
En agosto de 2020, Mozilla despidio a 250 de sus 1.000 empleados en todo el mundo como parte de una reestructuracion corporativa causada por el impacto a largo plazo de la pandemia de COVID-19. Entre los despedidos se encontraba la mayor parte del equipo de Rust, mientras que el equipo de Servo se disolvio por completo. El suceso desperto la preocupacion por el futuro de Rust.
A la semana siguiente, el equipo central de Rust reconocio el grave impacto de los despidos y anuncio que los planes para una fundacion de Rust estaban en marcha. El primer objetivo de la fundacion sería tomar la propiedad de todas las marcas y nombres de dominio, y también asumir la responsabilidad financiera de sus costes.
El 8 de febrero de 2021 la formacion de la Fundacion Rust fue anunciada oficialmente por sus cinco empresas fundadoras (AWS, Huawei, Google, Microsoft y Mozilla).
El 6 de abril de 2021, Google anuncio el apoyo a Rust dentro del proyecto de codigo abierto Android como alternativa a C/C++.
Componentes #
- Cargo
Cargo es el gestor de paquetes y dependencias para los programas Rust. Permite descargar e instalar dependencias para tus programas Rust.
Para crear un nuevo proyecto;
cargo new [project_name]
Para construir el proyecto;
cargo build [--release or not]
Para construir y luego ejecutar el proyecto;
cargo run
Para verificar el proyecto;
cargo check
- Rustfmt
Rust Format es el programa central que toma el programa de Rust como entrada y luego reemplaza los espacios y tabulaciones para formatear el codigo en cumplimiento de la guía de estilo de Rust.
- Clippy
Clippy es la herramienta integrada en Rust para mejorar el rendimiento y la legibilidad del codigo. Clippy tiene más de 400 reglas en 2021.
- Rustc
Es el compilador de Rust. Puede establecer argumentos por defecto a rustc estableciendo la variable; RUSTFLAGS . Mis argumentos reales para cada ejecucion de rustc son;
export RUSTFLAGS="-C target-feature=+crt-static -C target-cpu=native -C link-arg=-s"
- Rustup
Es una cadena de herramientas (un conjunto de herramientas) para instalar y utilizar rust para diferentes objetivos (sistemas y arquitecturas en las que se ejecutará el programa). También es la mejor manera de instalar y mantener actualizado rust, esto es porque es agnostico del sistema operativo y no necesita acceso de administracion/root para instalar rustc, cargo, etc-.
Para instalar rustup
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
Para actualizar rustup y el toolchain
rustup update
Para desinstalarlo
rustup self uninstall
Nota: Si queres hacer uso de la compilación cruzada, tenés que usar el toolchain por defecto que es para tu sistema y usar como target el de la compilación cruzada que queres |
---|
Teoría #
Rust como muchos otros lenguajes de programacion;
- Compilador
Es el componente que toma el codigo y lo transforma en codigo máquina (codigo binario, bits (0 y 1) con formato específico) con una estructura específica (no es solo leer y ejecutar las instrucciones, debe ser una estructura específica) e informacion. Con esas estructuras e informaciones adicionales, la CPU puede trabajar de forma específica y ejecutar correctamente lo que el desarrollador le indique.
- Pre procesador
Son opciones dentro del codigo que indican al compilador algunas cosas específicas a corroborar/hacer en el momento de la compilacion.
- Variable
Una variable es algo así como un “contenedor”, esto es porque puede contener un tipo de informacion; número, cadenas o caracteres. Identifica con un nombre [nombre_de_la_variable], la informacion almacenada en memoria, así que cuando quieras acceder a esa informacion puedes hacerlo usando el nombre de la variable como identificador.
Las variables en Rust se pueden crear con;
let [variable_name] = [value];
o si queres especificar el tipo de variable;
let [variable_name]: [type] = [value];
En Rust, si una variable no se utiliza intencionadamente en condiciones específicas, debe anteponerse un guion bajo; _[variable]
- Estructura
Es una coleccion de variables bajo la misma estructura fuera de la funcion específica. Se utiliza para agrupar variables con el mismo proposito.
- Funcion
Es una pieza de codigo que hace una cosa específica. En muchos lenguajes hay muchas formas de hacerlo pero en C,C++,Rust y otros la forma de hacerlo es poner el codigo entre; { y }. El compilador también debe saber cuando un trozo de codigo corresponde a una funcion, por lo que antes del nombre debe estar la palabra “fn”.
Por ejemplo;
fn [function_name] ( [arguments] ) -> [return_type] {
[code];
[code];
[code];
}
La funcion puede devolver al llamante un valor, por ejemplo; ejecutamos alguna operacion matemática y el retorno al llamante es el resultado de la misma.
La parte de la funcion en la que se declara la palabra clave “fn”, y el nombre de la funcion como argumentos y tipos de retorno se llama; firma.
- Método
A veces se llaman “objetos”. Es similar a las funciones pero un método se utiliza en el contexto de una estructura, enum o trait). Los métodos tienen como primer argumento “self”. Como las funciones, los métodos toman el retorno de la funcion y trabajan con ella.
- Argumentos
Un argumento es la informacion que se pasa al programa o a la funcion para trabajar con ella.
Verás en el futuro que habrá un argumento llamado “&self” que significa sí mismo. Que se utiliza cuando se crea un método que se utiliza para tomar los retornos de otra funcion.
- ASCII / UTF
ASCII y UTF son estándares de codificacion de caracteres (números, letras y símbolos) para la comunicacion electronica. Los UTF tienen variantes de 1,7,8,16 y 32 bits. Estos estándares utilizan una combinacion de bits para representar tantos símbolos como sea posible.
- Instruccion
Una instruccion es una orden de ejecucion. Puede ejecutar cualquier cosa que el lenguaje soporte.
Pero lo cierto es que cada instruccion que ejecuta tu codigo es realmente una funcion (o una macro).
- Comparaciones
Una comparacion en Rust solo puede hacerse entre valores del mismo tipo.
Solo puedes comparar variables, o valores, del mismo tipo. No puedes comparar un número entero con una cadena, por ejemplo, solo int con int, char con char y cadena con cadena. Sí, debes convertir todo a tabla ASCII si necesitas comparar pero te proporcionará más retos.
En Rust si pones en una comparacion " _ " significa “cualquier cosa”.
- Cabeceras / Bibliotecas
Cada lenguaje de programacion tiene cabeceras/bibliotecas. Son colecciones de funciones (y otras informaciones) en un archivo que cualquier desarrollador puede utilizar en su propio codigo. Por ejemplo, usted puede utilizar la biblioteca estándar y no tendrá que desarrollar la informacion y el estándar para proporcionar el conocimiento a rustc lo que es un int, char, etc.
- Socket
Un socket es un tipo especial de cosa. Un socket permite que un programa se comunique con otros programas transfiriendo informacion, este tipo de comunicacion puede ser en local (sockets de dominio Unix, en los que el programa puede comunicarse con otros en el mismo ordenador que se está ejecutando) o a través de internet (TCP/IP, UDP y otros). Sí, internet se basa en clientes y servidores que interactúan a través de sockets de internet en sus sistemas operativos.
- Depuracion
La depuracion es un proceso en el que se toma un programa XX y se analiza su funcionamiento (con la ayuda de símbolos específicos dentro del programa) para encontrar y solucionar problemas. El proceso permite también encontrar maneras de hackearlo y crackearlo. Siempre hay que liberar los programas a otros sin símbolos de depuracion.
- Extensiones de archivos
Los programas y librerías de Rust tienen la extension “.rs”.
- Espacios de nombres y objetos
Un nombre es un alias para los signos. Un signo permite identificar un recurso específico a un objeto específico. Si muchos objetos tienen las mismas funciones, llamadas y/o interfaces, el espacio de nombres puede permitir rastrearlas a un objeto específico.
De acuerdo, un espacio de nombres rastrea una cosa a un objeto específico, pero ¿qué es un objeto? En general y tratando de incluir todos los paradigmas; Un objeto es una pieza de cosa que tiene una interfaz (la forma en que interactúan con otros objetos). Dentro del objeto hay muchas piezas de informacion que pueden o no depender del entorno externo al objeto. Esa informacion dentro del objeto puede ser propiedades y atributos.
Esta explicacion sirve para la programacion, los sistemas operativos, las bases de datos, las redes, los contenedores, la virtualizacion, etc. Cada uno de ellos tiene una aplicacion específica de un objeto.
- Array
Un array es una coleccion de elementos (todos con el mismo tipo) bajo el mismo nombre, pero cada elemento tiene su propia posicion en la memoria.
- Puntero
Un puntero es eso. Un puntero a algo (informacion de la RAM, sobre variables, etc). En Rust se referencian con “&”, no tienen ninguna capacidad especial aparte de referirse a los datos, y no tienen sobrecarga.
- Puntero inteligente
A diferencia de los punteros normales, los punteros inteligentes amplían sus capacidades como estructuras de datos. Otra diferencia es que los “punteros inteligentes” toman la propiedad de los datos apuntados, los punteros normales no.
Los tipos “String” y “Vec” son punteros inteligentes.
- Crate
En Rust, un crate agrupará funcionalidad relacionada en un alcance para que la funcionalidad sea fácil de compartir entre múltiples proyectos. Esto es lo que en otros lenguajes se llama “headers” y/o “libraries”.
En un proyecto de Cargo, el crate librarie está en; “src/lib.rs”
La diferencia es que en Rust incluso “main.rs” (el futuro binario) es una creacion.
En Rust puedes encontrar el repositorio central de crates en; https://crates.io/
- Modulo
Como un crate es un grupo de funcionalidad, el modulo es el grupo de codigo. Un modulo contiene una o más funciones.
Así que un crate puede agrupar muchos modulos como los desarrolladores quieran, y cada modulo tiene funciones relacionadas (en el objetivo). Pero hay una cosa muy importante; un modulo puede tener dos tipos de codigo; un codigo privado (en el que solo el modulo o la funcion del modulo puede usarlo) o público (en el que puedes llamarlo desde tu codigo externo).
- Iterador
“Iterar” es la accion de procesar y trabajar con cada elemento de una serie, y un iterador es justo la forma de hacerlo.
En Rust los iteradores se compilan en un nivel mucho más bajo que otros codigos.
-
Ejecucion concurrente Es cuando diferentes partes de un programa se ejecutan independientemente.
-
Ejecucion paralela Es cuando diferentes partes de un programa se ejecutan al mismo tiempo. La principal diferencia dentro de la ejecucion concurrente es que las diferentes partes a pesar de ejecutarse al mismo tiempo, dependen unas de otras de alguna manera.
-
Hilo de ejecucion Es un trozo de codigo que se ejecuta de forma concurrente o paralela.
-
Compilación cruzada
Una compilación cruzada es una manera de compilar un binario desde una computadora con X procesador e Y sistema operativo a otra computadora con un sistema N y/o un procesador igual o difernete.
En las compilaciones cruzadas se debe usar el compilador base de tu sistema y el ’target’ debe ser el cruzado final que se busca.
Estructura del proyecto Cargo #
Cuando creas un nuevo proyecto con cargo (la forma recomendada de usar los proyectos de rust) con “cargo new [nombre_de_proyecto]” hay algunas cosas que debes saber;
- Puedes integrar git con cargo añadiendo el argumento; --vcs=git
- El archivo “Cargo.toml” es el archivo de configuracion del proyecto que contiene el nombre del proyecto, la version, la edicion y la misma informacion para todas las dependencias.
- Las secciones de Cargo.toml: “[profile.XXXXXX]”:
Aquí se personaliza “dev” y “release” especificando las banderas de compilacion en las secciones “[profile.XXXXX]”.
“[package]”:
Aquí puedes usar claves como; “name” (para el nombre del paquete), “license” (para especificar la licencia del paquete. Ten en cuenta que si publicas tus librerías en crates.io deben ser de codigo abierto), “version”, “description”, “version”, etc.
“[dependencies]”:
Aquí puedes especificar qué crates externas vas a utilizar dentro de tu codigo.
Y otras secciones.
- In src/ directory (or folder, is the same thing with differents name) you must put your program’s files and your headers / libs (crates). By default there is file; main.rs.
- If just exist “main.rs”, the crate root will be it. If exist “lib.rs” the crate root file will be it.
- In target/ directory you will find the executable program based in two things;
- The target (CPU architecture and operative system); is not the same compile for AArch64 using x86 than compile for BSD using Linux, each compilation will have the respective folder. My reccomendation is always compile for your specific CPU if you will not distirbute the binary file.
- If is a release or debug; just for internal use is reccomended use debug release, for release to public must be used “release” because with it will apply optimizations and the final executable will be smaller.
For complete understanding see online documentation;
https://doc.rust-lang.org/cargo/
Libraries #
You will can not do much without the respectives standard libraries. In Rust you can use libraries with; “use”. A library is a compilation of functions, structs and other code. All inside same file, so you can access many functions and features from another files:
use [library]::[specific_function_or_information];
Si no se especifica la funcion o la informacion a importar de esa biblioteca, se importará toda la lib.
Por defecto, como mínimo, necesitará:
use std::io;
En este punto sabes (con el ejemplo anterior) que tienes una lib llamada “std” (abreviatura de “standard”) y estás importando de ella una coleccion de funciones de “io”.
Variables #
Como dijimos antes, una variable es un contenedor que almacena un tipo específico de informacion.
Por defecto, el compilador de Rust interpreta la informacion que será asignada a la variable y formatea la variable a ese tipo. Si quieres especificar el tipo de variable la sintaxis es esta;
let [variable_name]: [type] ;
En Rust por defecto cuando se crea una variable es inmutable (lo que significa que no cambiará con el tiempo) así que si creaste la variable pero luego necesitas cambiar el valor, tienes dos opciones;
- Sombrearla; esto significa que declararás de nuevo la variable que sobrescribirá la antigua con un nuevo valor.
let var1 = 5;
[operations];
let var1 = 3;
- Configurarla como mutable; no es la mejor solucion, pero si sabes que el valor de la variable cambiará con el tiempo después de “let” especifica “mut”:
let mut [variable_name];
Tipos #
Hay muchos tipos de valores que puedes utilizar en tu codigo;
Números #
Hay dos tipos de números en Rust; enteros (conocidos como números sin parte fraccionaria. ), y números con decimales (conocidos como “float”).
- El tamaño de estos tipos (cuál es el valor más alto que puede contener) depende del número de bits que se utilicen para almacenarlo y representarlo.
- Los números enteros se representan como “iX” y los números flotantes como “fX”. La letra “X” es el número de bits a utilizar (8,16,32,64 y 128 bits están disponibles), los floats solo pueden utilizar 32 o 64 bits (en 2021 la velocidad en 64 bits es igual o superior a la de 32 bits, así que utiliza 64 bits).
let intvar1: i64 = 32000;
let floatvar2: f64 = 32.0001;
o si no especifica los tipos puede simplemente asignar el valor.
- Ambos (int y float) pueden tener signo (lo que significa que pueden ser positivos o negativos) o sin signo (lo que significa que pueden ser solo positivos). Los enteros y flotadores con signo se representan como “iX” y “fX” respectivamente. Los enteros sin signo se representan como “uX”.
- Los enteros pueden representarse como decimales (por defecto), hexadecimales (0x[XXXXX]), octales (0o[XXX]), binarios (0b[XXXXX]) y como un byte (solo u8) (b’X’).
Los números también tienen una buena característica: las tuplas. Una tupla es una forma general de agrupar un número de valores con una variedad de tipos en un tipo compuesto. Las tuplas tienen una longitud fija: una vez declaradas, no pueden crecer ni reducir su tamaño.
let variable1: (i32,i64,i8) = (128,32000,233);
Pero si intentas impresionar en pantalla la variable1 no podrás, eso es porque el compilador de Rust no sabe a qué valor estás haciendo referencia. Por eso Rust implementa otra buena característica para asignar una variable a cada valor de la tupla:
let variable1: (i32,i64,i8) = (128,32000,123);
let (x,y,z) = variable1;
Con el ejemplo anterior, creamos una tupla con un entero de 32 bits (el número 128), un entero de 64 bits (el número 32000) y un entero de 8 bits (el número 123). Luego creamos 3 nuevas variables (x,y y z) en las que asignamos los valores en orden (x -> 128, y -> 32000 y z -> 123). Este proceso se llama; desestructuracion.
Pero también las tuplas tienen la ventaja de indexar partes, por lo que usando el punto (".") puedes usar partes específicas de una tupla. Recuerda, en langagues (o en la mayoría de ellos) las tuplas, arrays y listas comienzan en cero (porque se basa en el número de cambio de posicion);
let variable1: (i32,i64,i8) = (128,32000,123);
let x = variable1.0;
Con abose X la variable tendrá el valor 128. Si el número era 1, X tenía 32000.
Símbolos #
Los símbolos pueden ser letras individuales (conocidas como “char”) y una cadena de caracteres (conocida como “string”). Técnica e internamente Rust llama a las letras como “grupos de grafemas”.
- Cuando es un carácter, el mismo debe estar entre comillas simples ‘X’.
- Cuando es una cadena, la misma debe estar dentro de comillas dobles “XXXX”.
- Las cadenas se representan como “str” en su forma más primitiva y “String” como la más moderna, la diferencia es que un tipo “str” es una secuencia inmutable de bytes UTF-8 y “String” es una secuencia mutable. Ambos son dinámicos (esto quiere decir que se pueden asignar directamente sin tener en cuenta el leght de entrada). Así que debes usar “str” cuando uses una cadena de entrada que no cambiará con el tiempo y debes usar “String” cuando necesites almacenar una cadena de entrada que cambiará/puede cambiar con el tiempo.
Nota; a diferencia de muchos otros lenguajes, en Rust no se pueden indexar palabras en una Cadena.
Las diferencias entre “str” y “String” son;
- String es cultivable, puede crecer con el tiempo.
- String es mutable, “str” no.
- String es owned, ver capítulo “Ownership”.
-
- Las cadenas soportan la codificacion “UTF-8”, que es la que se desea desde 2012. Por lo tanto, no siempre la longitud de una variable en la memoria será igual a los números de las palabras, debido a eso (sobre la codificacion UTF-8 y como almacenar las letras y los símbolos en la memoria) no se puede indexar palabras en una variable de cadena. Rust proporciona diferentes formas de interpretar los datos de cadena en bruto que los ordenadores almacenan para que cada programa pueda elegir la interpretacion que necesita, sin importar en qué lenguaje humano estén los datos, así que ten cuidado si almacenas datos en una variable de cadena como bytes, valores escalares o clusters de grafeno porque debes leer de la misma forma para leer correctamente los datos.
Algunas de las funciones en Strings son;
- “String::new()” : Crea una nueva variable de cadena vacía.
- “String::from(XX)” : Crea una nueva variable de cadena a partir de XX.
- “.push_str(XX)” : Añade “XX” a una variable de cadena existente.
- V1 + V2 : Puedes añadir la variable de cadena V2 al final de la variable de cadena V1. Pero “V1” y “V2” no serán más largas bajo puntuacion si no se usan por referencia, ver capítulo “Propiedad”. Este operador utiliza el método “add”, pero utiliza “str” como V2, así que tenga cuidado porque “str” y “String” no son lo mismo y pueden causar problemas.
- “format!(”{} {} {}", V1,V2,V3,VN) : Es lo mismo que “+”. Funciona igual que “println!” pero en lugar de imprimir en stdout, lo almacena en una variable como retorno. El tipo de retorno es String. Una cosa sobre esta opcion es que no toma la propiedad.
- “.to_string” : Convierte una cadena en un valor con formato de cadena.
- Booleanos; verdadero o falso.
Mientras que las tuplas solo pueden ser intergers, los tipos de listas de arrays pueden ser de cualquier tipo, pero solo un tipo para todos los elementos.
let list1 = [1,2,3,4,5,6];
let list2 = ['a','b','c','d','e','f'];
let list3 = ["Hello","World","Rust","rules!"];
Por lo demás, las listas de tuplas y arrays funcionan de la misma manera. Como en el ejemplo anterior; recuerda que los caracteres van con comillas simples y las cadenas con dobles.
¿Pero qué pasa si tienes una tupla con 35 tipos iguales? Puedes especificarlo con 2 declaraciones;
let variable1: [i32; 5] = [1,2,3,4,5];
Como puedes ver hay muchas diferencias; en lugar de () para el tipo, se especifica con [], el número de ese tipo se separa con “;” y la lista va con [] en lugar de (). ¿Y qué pasa si tienes 345 valores del mismo tipo? Pues lo mismo que antes pero ahora con valores en lugar de
let array5 = [34; 320000];
Con el ejemplo anterior, array5 es una variable con 320000 valores. Cada valor de esos 320000 tiene “34” como valor.
Cada valor dentro de una variable está indexado en la variable, de la misma manera que las tuplas. El primer valor (de izquierda a derecha) tiene la posicion cero (0), el siguiente tiene la posicion uno (1), etc. Pero en este caso, el acceso no es con “.” (punto), es con “[X]”:
let array6 = ["Hello"," ","World"," ","How"," ","are"," ","you","?"];
let variable7 = array6[0];
Con el ejemplo anterior, tenemos una variable llamada “array6” que tiene 10 posiciones (de 0 a 9. Recuerda que los arrays y las tuplas empiezan por cero). Como “variable7” se toma el valor de la posicion cero de array6, el valor es “Hola”, si el array era “1” entonces el valor es " " (espacio), y si era “9” entonces el valor es “?”.
Punteros #
Como dijimos en la seccion de teoría, un puntero es eso; un puntero a algo. Ese “algo” puede ser cualquier cosa que exista; alguna informacion en la RAM, otras variables, etc.
Por ejemplo;
fn main() {
let information = "Hello World".to_string();
let variable1 = &information;
println!("{}",variable1);
}
El codigo anterior creará una variable llamada “informacion” que contiene la cadena “Hola Mundo” pasada como corresponde por la funcion “.to_string()”. Esto último permite imprimir una cadena en pantalla.
Entonces la variable “variable1” es un puntero; porque está haciendo referencia a la memoria RAM de “informacion” con el símbolo; &
Así que “variable1” será un puntero a “informacion”. Cuando el segundo cambio, el primero también.
En el codigo anterior “variable1” apuntando a “informacion” está tomando como informacion a “Hola Mundo”.
Hay muchas formas de utilizar los punteros, pero es la más sencilla.
Slice #
Es más utilizado en los retornos de las funciones, el slice se especifica con; “?” y significa que no se sabe exactamente lo que se va a devolver. Es muy útil cuando se obtiene alguna informacion general (como un String) y no se sabe que es lo que se va a devolver específicamente.
Ejemplo;
fn function1() -> ? {
[code];
[return_known]
}
Estructura #
Una estructura, es un tipo que permite agrupar variables dentro de un dominio logico. Al igual que las tuplas, en las estructuras hay que declarar las variables que contendrán los valores.
Cada variable dentro de la estructura se llama campo. Una estructura sin archivos se llama estructura unitaria
Sintaxis;
struct [struct_name] {
[variable1]: [type],
[variable2]: [type],
[variableN]: [type],
}
Para acceder a modificar o leer los valores, primero hay que crear una variable. Esa variable tendrá la propiedad de esa estructura, y a través de esa variable se accederá a la estructura. Cuando creas esa variable, no debes asignar un tipo, ya que el valor debe ser [nombre_de_la_estructura] y luego especificarás a través de { …} el valor de cada componente.
Pero si esa variable fue declarada antes puedes usar esta sintaxis para leer/escribir (si esa variable es mutable, por supuesto); [nombre_variable].[componente_estructura]
Por ejemplo;
struct User{
email: String,
age: u32,
}
fn main() {
let mut var1_structure = User{
email: String::from("shyanjm_protonmail.com"),
age: 0,
};
var1_structure.age = 43;
println!("Struct User from instance var1_structure: {} {}", var1_structure.email, var1_structure.age);
}
Como puedes ver, hay algunas diferencias al usar structs:
- Los valores se asignan con : no con igual =.
- Cuando se inicializa el struct, hay que initear todas las variables, aunque se les proporcione el valor correcto antes.
- Usando el punto . puedes acceder a los datos sin problemas si la variable fue declarada antes (incluso si son otros structs).
- No se puede impresionar directamente una estructura, porque Rust no sabe como formatearla.
Y si quieres devolver un struct, simplemente pon el nombre del struct como tipo de retorno. Pero no puedes pasar una estructura directamente, debes pasar los componentes.
Si integras una estructura en una funcion, y si el nombre del argumento es exactamente el mismo que los campos, puedes poner el nombre y Rust asignará directamente el valor (esto se llama; Init shortland). Pero en mi opinion, es solo una palabra, por lo que poner los nombres es una de las mejores prácticas que puedes hacer; hacer que tu codigo sea fácil de leer para otros sin importar el nivel de experiencia.
Otra cosa que puedes hacer para ahorrar tiempo es asignar, con una operacion de copia, el resto de variables de la estructura A a la estructura B. Como he dicho antes, en mi opinion es mejor escribir más pero hacer el codigo más fácil de leer y entender que ahorrar unas pocas líneas de codigo, pero aquí la sintaxis;
let user2 = User {
email: String::from("another@example.com"),
..user1
};
Ese codigo creará una instancia de la estructura de usuario en el campo email y hará una operacion MOVE del resto de campos de user1 para que no estén disponibles en la estructura de user1. Para esta parte ve a la seccion “Propiedad”.
También como en el caso de los structs se pueden crear tipos aduaneros usando las primitivas (enteros, floats, etc) pero recuerda, no asignes el struct como tipo, el struct asignado como valor es el tipo self. Con ello al igual que vimos antes puedes agrupar los tipos en tuplas, la sintaxis es;
struct [struct_name]([type],..,..);
por ejemplo;
struct Position3d(i64,i64,i64);
let position1 = Position3d(64,32,0);
Hay algunos puntos sobre las tuplas struct que debes tener en cuenta cuando trabajes con ellas;
- Una tupla struct no puede ser pasada como argumento/parámetro a otra.
- Todavía puede acceder a cada parte de la tupla normalmente como cualquier tupla con punto . o como array.
Funciones #
La primera funcion que verás es “main”.
fn main(){
[code];
}
Esa funcion es donde su programa comienza. Una funcion no se ejecutará a menos que sea llamada dentro de “main”.
Si vienes de C, C++ y otros sabes que cada funcion después de “main” producirá un error al intentar compilar y por eso debes usar prototipos para arreglarlo. En Rust esto no es necesario, puedes poner todas las funciones que quieras después de “main”.
Verás que dentro de la funcion “main” hay “println!”. El “!” indica que no es una funcion, es una macro (lo veremos en el futuro).
Las funciones tienen esta sintaxis;
fn [function_name]( [arguments] ) -> [Return_type] {
[code];
[code];
...
}
Con el ejemplo anterior;
-
[function_name]
Este es el nombre de la funcion, se utiliza para saber como llamarla y donde se llama.
-
[arguments]
Los argumentos son las opciones que se pasan al programa.
-
[Return_type]
Una funcion puede devolver a quien la llama un valor. Este valor puede ser cualquier cosa reconocida como tipo en Rust .
Dentro de Rust el retorno se especifica en el codigo de la funcion con la palabra “return” o simplemente poniendo una variable o valor. Así que ese valor será devuelto a la variable o funcion que lo llamo. En lugar del resto del codigo, el retorno va sin el punto y coma “;” al final de la línea.
De StackOverflow;
Se puede omitir la palabra clave return solo si el valor devuelto es la última expresion del bloque de funciones, de lo contrario es necesario utilizar explícitamente return
Si el argumento se especifica así
&[String]
Significa que el argumento es un array de cadenas, se puede acceder directamente como un array.
Hay una lista de funciones y macros comunes.
println! #
Se utiliza para imprimir en pantalla. La sintaxis es;
println!(" [text] {}", variable_X);
El [text] es la cadena de texto que se quiere imprimir en pantalla.
Los “{}” indican a Rust que debe existir el valor de la “variable_x”, para en tiempo de ejecucion reemplazar los {} con el valor de la variable_x. Puedes añadir tantos {} y variables como quieras.
“Println!” imprime el mensaje a stdout.
eprintln! #
Funciona de la misma manera que “println!” pero imprime el mensaje a stderr. Que se utiliza para imprimir solo los problemas.
String #
Esta librería asignada en rust estándar devuelve una cadena.
Hay muchas formas de almacenar y usar cadenas en variables.
- Para crear una cadena vacía en una variable:
let [variable_name] = String::new();
- Para almacenar un string en una variable:
let [variable_name] = "[TEXT]".to_string();
o
let [variable_name] = String::from("[TEXT]");
o
let [variable_name]: String = "[TEXT]".into();
Esta última línea “into” transformará [TEXT] en String, porque es el tipo de texto.
- Para añadir una cadena a otra variable de cadena:
[variable_name].push_str([TEXT_OR_STRING_VARIABLE]);
o
[variable_1] + [variable_2]
o
[variable_to_store] = format!(".... {} {} {} {} ...", var1, var2, var3, varn);
- Para convertir de string a un vector de letras:
[string_variable].chars()
es una buena opcion usar esto con una iteracion.
- Para iterar sobre cada letra:
for X in [string_variable].chars() {
.....
}
.to_lowercase() #
Esta funcion hace lo que su nombre indica, transformar alguna Cadena en equivalente a minúsculas.
La sintaxis es; ``rust [str_o_cadena_variable].a_minúsculas()
### .contains(XXXX)
Este método/funcion toma la entrada y procesa si tiene "XXXX". La entrada puede ser "str", String, char, o slice of chars.
Devuelve "True" como '()' si es correcto o "False" si no se encontro nada.
La sintaxis es;
```rust
let cadenas = "hola mundo".to_string();
let return1 = assert!(cadenas.contiene("mundo"));
.lines() #
Este método/funcion toma el imput y crea una iteracion sobre cada línea (puedes establecer la nueva línea en una cadena con “\n”). Puedes ir a la siguiente línea con “.next()” y si vas al final de la línea el retorno será “None”.
La sintaxis es;
let texto = "foo\r\nbar\n\nbaz\n";
let mut lines = text.lines();
assert_eq!(Some("foo"), lines.next());
assert_eq!(Some("bar"), lines.next());
assert_eq!(Some(""), lines.next());
assert_eq!(Some("baz"), lines.next());
assert_eq!(None, lines.next());
io::stdin() #
Como puedes ver en el nombre, la funcion es “stdin” de la lib “io”. Esta funcion toma la entrada y la procesa dentro de una estructura (en un buffer global), para que pueda ser manejada adecuadamente en una variable.
La sintaxis es;
let [mut_o_no] [nombre_variable] = io::stdin();
o si tienes una variable previamente mutable formateada como tipo cadena:
let [mut_or_not] [nombre_variable2] = stdin.read_line(&mut [nombre_variable])?;
Necesitamos crear una nueva variable porque “stdin” tiene un valor de retorno que debe ser almacenado en algún lugar, a menos que uses “expect”. Como el imput se almacena dentro de un buffer global, puedes forzar el bloqueo de ese buffer para que tu thread sea el único que pueda acceder y leer/escribir en él. Con esto usas una sincronizacion explícita;
let stdin = io::stdin();
let mut [nombre_var1] = stdin.lock();
¿[nombre_variable].read_line(&mut [nombre_variable])?;
Esperar() #
Este es un objeto especial. Se usa si quieres imprimir en pantalla algo cuando la funcion anterior da error.
[funcion_anterior_o_variable].expect("[TEXTO]");
Con el ejemplo anterior si por alguna razon la primera funcion va a error se pasará a “expect” e impresionará [TEXT].
Ten en cuenta esto; “expect” toma “str” como entrada, por lo que no puedes pasar una cadena directamente, necesitas convertirla primero. Ese retorno “str” de la funcion, objeto, variable o método anterior es un retorno “Err”. No es un retorno normal, el tipo es “Err”.
read_line( [variable] ) and read_line_prompt(msg: &str) #
Este objeto funciona con “stdin()” porque lee una línea (atencion, una línea) y la almacena en [variable] que debe ser una variable formateada como cadena.
stdin().read_line(&mut [variable]);
Puedes usar la variacion; read_line_prompt(msg: &str).
Esa funcion muestra al usuario la cadena pasa como argumento ( el texto beetwen cuotas dobles ) antes de llamar a read_line para almacenar la entrada.
parse() #
Como muchas funciones, este es uno de los objetos más útiles en Rust.
Sintaxis;
[variable_target]: [nuevo_tipo] = [variable1].[otro_objeto_o_no].parse()
Parse transformará un trozo de cadena de [variable1] en otro tipo, generalmente en el tipo objetivo de la variable.
Seguro que utilizarás mucho este método.
trim() #
Este es un objeto muy útil.
Sintaxis;
[variable].[funcion_recortar];
-
“trim()” elimina los espacios en blanco iniciales y finales y las tabulaciones de una cadena.
-
“trim_start()” solo elimina los espacios en blanco iniciales y las tabulaciones de una cadena.
-
nota
trim_left ha sido reemplazado por “trim_start”.
-
-
“trim_end()” solo elimina los espacios en blanco finales y las tabulaciones de una cadena.
-
nota
trim_right ha sido reemplazado por “trim_end”.
-
-
“trim_matches(’[X]’)” elimina el patron [X] de las posiciones iniciales y finales de una cadena.
-
trim_start_matches(’[X]’)" elimina el patron [X] de las posiciones iniciales de una cadena.
-
trim_end_matches(’[X]’)" elimina el patron [X] de las posiciones finales de una cadena.
strip #
Elimina de las cadenas el patron [XXXX]. Obviamente el retorno es una cadena a la variable pasada.
Sintaxis;
[variable].[strip_function];
- “strip_prefix("[XXXX]”) " elimina el patron [XXXXX] de las posiciones iniciales de una cadena.
- “strip_suffix("[XXXX]”) " elimina el patron [XXXXX] de las posiciones finales de una cadena.
is_ascii() #
Comprueba si la cadena tiene valores ASCII. Devuelve un tipo booleano.
Sintaxis;
[variable].is_ascii();
len() #
Devuelve el número de bytes del tamaño de la variable. Atencion; “número de bytes”, NO “número de chars/etc” (sí, en la tabla ASCII cada valor usa 1 byte pero no será así para siempre). Devuelve interger.
Sintaxis;
[variable].len();
is_empty() #
Comprueba si la variable no tiene informacion en su interior. Devuelve un tipo booleano.
Sintaxis;
[variable].is_empty();
as_bytes() #
Transforma cada caracter en su equivalente en bytes. Devuelve el tipo entero.
Sintaxis;
[variable].as_bytes();
[variable].as_bytes_mut();
Assert #
Assert es una familia de macros para comparar una o dos cosas. El único comportamiento común es que encuentran que no coinciden con el resultado esperado, llamará ¡panic! y terminará el programa.
Esto se aplica no solo para variables y tipos comunes, también para retornos.
- Para comprobar si el retorno de algo es ‘True’;
assert!([cosa], [mensaje_para_mostrar_si_tiene_panico], [variables_si_el_mensaje_de_panico_tiene_variables]);
- Para comprobar si dos cosas son iguales;
assert_eq!([thing_1],[thing_2],[message_to_show_if_panic], [variables_if_panic_message_have_variables]);
“[cosa_1]” se llama por oxido “izquierda” y “[cosa_2]” se llama por oxido “derecha”.
- Para comprobar si dos cosas no son iguales ``rust assert_ne!([thing_1],[thing_2],[message_to_show_if_panic], [variables_if_panic_message_have_variables]);
"[cosa_1]" se llama por oxido "izquierda" y "[cosa_2]" se llama por oxido "derecha".
### Contiene
Las funciones Contains devuelven true si XXX está en el valor.
Hay muchas implementaciones y crates para probar bajo scope dependiendo del escenario y la variable objetivo;
1. std::slice::contains
2. std:str::contains
3. std::option::Option::contains
Etc.
### .clone()
Algunos no se copian implícitamente. El método clone implementa los comportamientos adecuados para copiar valores entre variables sin problemas.
Por ejemplo;
```rust
let var1= String::from("Hola Mundo");
let var2= var1.clone();
is_ok() #
Este método comprueba si los resultados de algún tipo son “Ok(X)”. Devuelve “true” o “false”.
Por ejemplo; ``rust let var1: Result<&str,&str> = Ok(“Todo está bien”); assert_eq!(var1.is_ok(), true );
## Flujo de Control; if, else, else if, loop, while y for
Hay muchas formas de controlar el flujo del programa, todas esas formas son con una comparacion condicional; si esto es [algo] a esto otro, haz esto, si no haz esto otro.
### if, else y else if
La declaracion "if" es lo que piensas. Si la condicion es correcta haz el codigo dentro de "{}" si no continúa. Esta declaracion puede trabajar con números, cadenas y cualquier tipo estándar de oxido.
La condicion puede estar dentro de "()" pero probablemente el compilador de rust te dirá que los elimines, esto es porque intentará que lo evites si no son necesarios.
Por ejemplo para comparar dos números;
```rust
fn main() {
let var1 = 45
let var2 = 300;
if var1 < var2{
println!("{} es menor que {}",var1,var2);
}
}
El codigo anterior probará si “var1” (con valor 45) es menor que “var2” (con valor 300), si esa condicion es cierta ejecutará el codigo whitin “{}”, en este caso imprimirá en pantalla.
La sentencia “if” no solo puede funcionar con números, como he dicho antes; puede funcionar con cualquier tipo estándar de oxido.
En la condicion puedes usar cualquier funcion, incluso sus retornos;
fn main() {
let var1 = "Hola Mundo";
if var1.is_empty() != true {
println!("{} no está vacío",var1);
}
}
El codigo anterior creará una variable con la cadena “Hola Mundo”, luego probará si “var1” está vacía.
Si el retorno del if es “true” no ejecutará nada, pero si no es (eso significa “!=”) true ejecutará el codigo dentro de “{}”.
Pero, ¿qué pasa si quieres hacer una prueba y tomar más de una accion teniendo en cuenta los retornos de la condicion? Fácil;
if [condicion] {
[code_to_execute_if_condition_true];
}
else if [segunda_condicion]{
[code_to_execute_if_the_first_condition_was_false_but_this_second_is_true];
}
else {
[code_to_execute_if_all_conditions_are_false];
}
Puedes utilizar tantos “else if” como necesites. Como puedes pensar, todos los retornos de las condiciones son de tipo bool (True o False).
Aquí algunas de las operaciones if, while, for y else (recuerda que puedes usar opteraciones normales, no solo aritméticas);
- a < b
Si a es menor que b.
- a > b
Si a es mayor que b.
- a == b
Si a es igual a b. No confundir con un igual simple; “=” que es un asignador, no una comparacion.
- a
Comprueba si a es igual a “True” (el valor booleano).
- !a
Comprueba si a es igual a “Falso” (el valor booleano).
El programa anterior es simple pero utiliza todos los conocimientos adquiridos hasta ahora. Primero importa el create; InputOutput de la librería estándar.
Luego crea una funcion llamada “f_t_c_fn” que toma como argumento “user_input” con tipo “String” (yo uso el mismo nombre del argumento que la variable que paso a la funcion, pero no es necesario).
En la funcion “f_t_c_fn”, crear una variable mutable llamada “user_input2” con tipo “string” asignando como valor una cadena vacía. A continuacion cree dos variables float de 64bits llamadas “f_t_c” y “is_number”.
Pida al usuario que impute valores leyendo la línea desde imput/otput estándar, guardando los valores en la variable mutable “user_input2”. Si los valores imput fallan, mostrar el error “Error taking imput.”.
El codigo bueno y el uso de objetos está aquí; el “user_input2” se pasará al objeto “trim”, el resultado se pasará al objeto “parse”, el resultado de esto se pasará a la variable “is_number”. Recuerde, que la variable es 64bits float, por lo que si la conversion (por “parse”) se hizo correctamente, el resultado se asigna a “is_number”, si hay un error con la conversion se pasa a “expect”.
Luego comienza la comparacion, y en mi opinion tomen esto con cuidado (no porque deban leer esto con cuidado, porque deben entender porque lo wrotte) y con mucha atencion. Empezamos una comparacion con “if” e incluimos todas las comparaciones dentro de “()” porque no estamos haciendo solo una comparacion; estamos haciendo dos comparaciones). Primero borramos todos los espacios posibles al principio y al final de “user_input” para comprobar si es igual (recuerda, dos ==, un = es para asignar valores) a “C”, pero “C” o cualquier otro valor alfabético no puede ser comparado directamente con una cadena así que después de la cadena la pasamos al objeto de llamada “.to_string” para hacer la conversion correctamente. Luego se especifica la condicion exclusiva “or” con “||” (“and” es “&&”) para comprobar lo mismo pero con la cadena “Celsius”.
Tenga en cuenta esto sobre los condicionales “or” y “and” en “if”/“else if”; cuando usa “or” || con dos o más condicionales, es suficiente que solo uno sea verdadero para ejecutar el codigo dentro de los “{}” de if. Cuando usas “and” && con dos o más condicionales, todos ellos DEBEN ser verdaderos en cada uno para ejecutar el codigo.
Entonces, si las condicionales son verdaderas se muestran en pantalla dos variables; primero user_input y luego “user_input +/- f_t_C” dependiendo de la seleccion del usuario.
Entonces cuando el programa se ejecuta, toma la entrada del usuario y la pasa a la funcion “f_t_C_fn” y hace las operaciones apropiadas.
Propiedad #
Gestion de la RAM #
Toma la RAM como una pila;
|--------------|
| Valor 5 |
|--------------|
| Valor 4
|--------------|
| Valor 3
|--------------|
| Valor 2
|--------------|
| Valor 1
|--------------|
El tamaño de la pila depende de muchas cosas; el sistema operativo, como la CPU configura los marcos de la RAM, la arquitectura de la CPU, etc.
La pila sstack almacena valores de la forma; “primero en entrar, último en salir” o “último en entrar, primero en salir”. Así que el primer valor “Valor 1” está por debajo del “Valor 2” y así sucesivamente, si quieres recuperar un valor de la pila, debes empezar desde arriba hacia abajo, así que si quieres el “Valor 3” debes mover primero el 4 y el 5 a otro lugar antes de coger el 3.
Añadir valores a la pila se llama “push” y recuperarlos se llama “pop”.
La accion “push” almacena valores en la parte superior de la pila, y la accion “pop” recupera valores de la parte superior de la pila.
En la página web de Rust se explica perfectamente;
Todos los datos almacenados en la pila deben tener un tamaño fijo conocido. Los datos con un tamaño desconocido en tiempo de compilacion o un tamaño que pueda cambiar deben almacenarse en el monton. El monton está menos organizado: cuando pones datos en el monton, solicitas una cierta cantidad de espacio. El asignador de memoria encuentra un lugar vacío en el monton que sea lo suficientemente grande, lo marca como en uso y devuelve un puntero, que es la direccion de esa ubicacion. Este proceso se denomina asignar en el monton y a veces se abrevia como simplemente asignar. Colocar valores en la pila no se considera asignacion. Como el puntero tiene un tamaño fijo y conocido, puedes almacenarlo en la pila, pero cuando quieras los datos reales, debes seguir el puntero.
Empujar a la pila es más rápido que asignar en el monton porque el asignador nunca tiene que buscar un lugar para almacenar nuevos datos; esa ubicacion siempre está en la parte superior de la pila. Comparativamente, la asignacion de espacio en el monton requiere más trabajo, porque el asignador debe encontrar primero un espacio lo suficientemente grande para almacenar los datos y luego realizar la contabilidad para preparar la siguiente asignacion.
Acceder a los datos en el monton es más lento que acceder a los datos en la pila porque hay que seguir un puntero para llegar allí. Los procesadores actuales son más rápidos si saltan menos en la memoria.
Cuando tu codigo llama a una funcion, los valores pasados a la funcion (incluyendo, potencialmente, punteros a datos en el monton) y las variables locales de la funcion son empujados a la pila. Cuando la funcion finaliza, esos valores son retirados de la pila.
Hacer un seguimiento de qué partes del codigo están utilizando qué datos del monton, minimizar la cantidad de datos duplicados en el monton y limpiar los datos no utilizados en el monton para que no te quedes sin espacio son todos problemas que aborda la propiedad. Una vez que entiendas la propiedad, no necesitarás pensar en la pila y el monton muy a menudo, pero saber que la gestion de los datos del monton es la razon por la que existe la propiedad puede ayudar a explicar por qué funciona de la manera que lo hace.
Reglas de propiedad #
En Rust hay tres reglas sobre la propiedad;
Cada valor en Rust tiene una variable que se llama owner.
Solo puede haber un propietario a la vez.
Cuando el propietario sale del alcance, el valor será eliminado.
Memoria #
En todos los lenguajes de programacion, los datos almacenados y gestionados por el programa deben estar en memoria. Aquí hay dos opciones posibles;
-
El programador es el responsable de solicitar memoria y liberar la memoria no utilizada.
-
El lenguaje tiene un recolector de basura (GC) que analiza la memoria del programa y libera la memoria no utilizada.
Bueno Rust es más como el primero, el programador es el responsable de solicitar la asignacion de datos en la memoria y la liberacion cuando no se utiliza. En Rust como en muchos otros lenguajes la forma más común de hacerlo es usando funciones. Pero, en general, todos los lenguajes de programacion funcionan de la misma manera cuando se trata de pila y monton.
Rust como otros lenguajes, tiene un método para controlar la informacion. Supongamos que tienes este codigo;
fn funcion1(){
let mut var1 = String::from("Hola ");
var1.push_str(" Mundo");
}
fn funcion2(){
let mut var1 = String::from("¿Como estás?");
println!("{}",var1);
}
En el codigo anterior tenemos una variable llamada “var1” una en la funcion1 y la otra en la funcion2, cuando (en la funcion1) el bloque de codigo termina, la memoria asignada en la pila para almacenar “Hola Mundo.” es liberada y limpiada, así que cuando la “funcion2” crea una variable con el mismo nombre y diferente valor en la pila, la otra informacion no chocará con esta nueva.
Rust ejecuta una llamada ‘drop’ cuando un bloque de codigo termina.
Esto es lo más básico sobre la propiedad en Rust; cada variable y valor es válido (y almacenado en RAM) hasta que el bloque de codigo termina. Pero no funcionará si estás usando una variable externa (como una variable externa normal, argumentos de funciones o struct por ejemplo). Así que recuerda; modula tu codigo en funciones según necesites hacer cosas diferentes, solo pasa datos a una funcion usando structs, argumentos de funciones o variables externas SoLO y SoLO si realmente necesitas esos datos y NO devuelvas datos fuera de la funcion si REALMENTE no los necesitas, y también si tienes una variable “x” que contiene informacion solo para la funcion “y”, no crees esa variable en la funcion “main” (un hábito de C o C++ de las Universidades generalmente), créala dentro de esa funcion “y” para que la propiedad de Rust pueda funcionar correctamente. alcance de la variable
Digamos que tenemos este trozo de codigo:
let var1 = String::from("Hola Mundo.");
let var2 = var1;
Tenemos una variable llamada “var1” que contiene en memoria el valor; “Hola Mundo.” pero, a no ser que estes pensando, la variable no tiene ese valor directamente en la pila. En la pila de var1 solo está la informacion sobre un puntero (que apunta, como su nombre indica, a la ubicacion del monton donde se almacena la informacion completa, en este caso “Hola Mundo”), el tamaño entero (que contiene en bytes el tamaño de los datos asignados en el monton) y la capacidad entera (que contiene en bytes el tamaño máximo que la variable puede almacenar en el monton utilizando el puntero).
alcance de la variable y gestion de memoria #
Digamos que tenemos este trozo de codigo:
let var1 = String::from("Hola Mundo.");
let var2 = var1;
Tenemos una variable llamada “var1” que contiene en memoria el valor; “Hola Mundo.” pero, a menos que tal vez estés pensando, la variable no tiene ese valor directamente en la pila. En la pila de var1 solo está la informacion sobre un puntero (que apunta, como su nombre indica, a la ubicacion del monton donde se almacena la informacion completa, en este caso “Hola Mundo”), el entero size (que contiene en bytes el tamaño de los datos asignados al monton) y el entero capacity (que contiene en bytes el tamaño máximo que la variable puede almacenar en el monton usando el puntero).