En el post de los primeros conceptos ya vimos un ejemplo con algunas cositas interesantes como el Shadowing
. En esta entrada vamos a ver cuáles son los tipos de datos en Rust, su uso, longitud y características generales. Si tienes algunos conceptos de programación, mucho mejor, entenderás más rápido los tipos de datos.
Tipos de datos en Rust
Tipos escalares (Scalar Types)
Este tipo de dato representa un solo valor, es decir, solo almacena una unidad de algo, por ejemplo, un i32
solo puede guardar un número entero de 32 bits, en comparación con los tipos compuestos, como un array
, que guarda una colección de valores, separados e identificados por su índice. Aquí solo hay un matiz en el tipo &str
ya que este, aunque es una colección de caracteres UTF-8, es inmutable en tiempo de ejecución, así que Rust ya sabe la secuencia en memoria una vez que se ejecuta el programa, tomando este valor como una secuencia única.
Strings
Estas son las cadenas de texto normales. Las veremos un poco en detalle, cuando veamos los Slices
(en otro post), pero por ahora quedémonos con que hay dos tipos:
&str
: es una referencia inmutable de un “array” dechar
.String
: es un búfer que se puede modificar en tiempo de ejecución, es por ello que en ejemplo anterior usamos este para capturar el número del usuario.
Enteros
Los enteros son números sin fracción, es decir, representan números “completos”, nada de 10.455
, aquí solo valen números como 100
o negativos que sigan la misma regla, o sea -122
.
Longitud | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
Como vemos en la tabla, pueden existir números con signo (signed
) o sin signo (unsigned
), es decir, según el número que quieras procesar, debes usar el tipo correcto. Si queremos realizar operaciones que sean negativas, por ejemplo, mi cuenta de banco 🤑, hay que usar los tipos signed
, porque puede ser que el valor resultante de una operación pueda ser negativo, pero si queremos capturar edad, por ejemplo, nadie puede tener -10
años, entonces debemos usar unsigned
.
¿Qué significa esos números después de i
o u
? Pues se usan para hacer el cálculo del rango de números aceptado.
Para números signed
, es decir, para valores que pueden ser negativos o positivos, la fórmula es: -(2^(n-1)) hasta 2^(n-1) - 1
. Por ejemplo el tipo i8
sería:
-(2^8-1) hasta 2^(8-1) -1
-(2^7) hasta 2^7-1
-128 hasta -127
Los números unsigned
, o sea, los valores que solo aceptan positivos, la fórmula es 0 hasta 2^n - 1
. Un ejemplo:
0 hasta 2^8 -1
0 hasta 256 -1
0 hasta 255
isize
y usize
dependen del tipo de arquitectura que estés usando, vale 64 bits si estás usando ese procesador o 32 bits si estás usando ese tipo de procesador.
Rust tiene algo curioso al momento de escribir directamente valores al declarar variables:
Literales | Ejemplo |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b'A' |
El número decimal 98_222 sería igual a 98,222.
En Rust lo octales se representan con 0o
. En este caso 0o77
sería 63 decimal.
Para los números binarios el prefijo es 0b
y en este caso el número sería 1111 0000
o sea 240
decimal.
Tipos flotantes
En Rust todos los números que aceptan puntos decimales son signed
es decir, que pueden aceptar tanto valores negativos como positivos.
Longitud | Signed | Precisión |
---|---|---|
32-bit | f32 | Simple |
64-bit | f64 | Doble |
Por default, todos son f64
pero puedes declarar explícitamente cuando uno debe ser de 32 bits:
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
Tipo char
Este tipo es básicamente cualquier caracter, incluso puede ser un caracter UNICODE, como los emojis. Este tipo de datos tiene 4 bytes, si cada byte tiene 8 bits, entonces tenemos que representa 8x4=32
entonces 2^32 = 4,294,967,296
. Un poquito más de valores que es ASCII.
fn main() {
let c = 'z';
let z: char = 'ℤ'; // declaración explícita
let heart_eyed_cat = '😻';
}
Fijémonos muy bien en que están en comillas simples, no dobles, como las cadenas de texto normales.
Tipo Boolean
Pues no se necesita mucho para adivinar que Boolean es true
o false
. En Rust se declara como bool
.
fn main() {
let t = true;
let f: bool = false; // declaración explícita
}
Tipos Compuestos (Compound Types)
En la sección anterior vimos valores escalares, cuya característica es que sus variables guardan un solo valor. En contraparte, uno de los tipos de datos en Rust que son muy importantes son los tipos compuestos. Estos pueden guardar multiples valores en una sola variable. Rust tiene dos tipos compuestos: tuplas (tuples
) y arrays.
Tipo Tupla en Rust (tuples)
Una tupla es una estructura de datos que agrupa diferentes tipos de datos en un solo tipo compuesto. Las tuplas tienen una longitud fija. Una vez que se crean no se puede modificar.
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
En este ejemplo, la variable tup
tiene tres diferentes tipos de datos, i32
, f64
y u8
. A esta misma variable podemos hacerle destructuring
(sí, como en Javascript con const {a, b, c} = objeto
, que básicamente es dividir tup
en tres diferentes variables en la asignación. Más claro:
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
}
En este ejemplo vemos que asignando una serie de variables a tup
. Podemos acceder directamente llamando al nombre de la variable que le hemos asignado, sin embargo, hay otra manera de hacerlo:
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
Una tupla vacía tiene un nombre especial en Rust, este es unit
. Se representa como ()
, entonces cualquier expresión regresa una unit
implícitamente si no indicamos lo contrario. Es por ello que podemos usar match
en el ejemplo del post anterior. Básicamente lo que hacemos es gestionar la respuesta de una función que está devolviendo una tupla.
Tipo array en Rust
A diferencia de las tuplas, que pueden tener diferentes tipos de datos, en los arrays, cada tipo de dato incluido en la variable debe ser el mismo. En pocas palabras, en un array de u32
todos sus elementos deben ser enteros positivos de 32 bits. Pero al igual que las tuplas, debe tener una longitud fija y no se puede modificar en tiempo de ejecución.
Declaración:
fn main() {
let a = [1, 2, 3, 4, 5];
}
Si debo ponerles una longitud ¿entonces qué puedo usar para una lista de valores cuya longitud desconozco? Tranquilo, ya los veremos más adelante, se llaman vectores
(vectors
) o slices
, pero ya llegaremos ahí.
En el siguiente ejemplo vemos como declarar un array y dejar que Rust determine el tipo de dato del array, en este caso es &str
:
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
Si queremos declarar explícitamente el tipo de dato que el array va a guardar hacemos lo siguiente:
let arr: [i32; 5] = [1, 2, 3, 4, 5];
Aquí hemos declarado una variable llamada arr
que guardará 5
elementos de tipo i32
y le asignamos los valores del 1 al 5.
Vamos a otra manera de declarar un array:
let a = [3; 5];
Acá lo que vemos es que declaramos la variable a
que contiene 5
elementos (este no cambia de posición), sin embargo, ahora, en lugar de declarar el tipo, que antes fue i32
, ahora le decimos el valor que va a repetir el número de elementos (5
), que en este caso es el 3
. Entonces sería lo mismo que hacer lo siguiente:
let arr = [3, 3, 3, 3, 3];
Acceder a los elementos del array
Para acceder a los elementos de un array en Rust, debemos hacerlo mediante el index
de cada elemento.
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
Podemos verificar la longitud de un array con el método len
.
fn main(){
let ages:[i32;5] = [10,22,30,4,18];
println!("Length of ages array: {}", ages.len());
}
Por el momento ha sido todo sobre los tipos de datos de Rust.
Poco a poquito.
Gracias por leer.