A continuación veremos todos los aspectos basicos y avanzados de Rust.
Primero que todo el famoso "Hello World!". En un jupyter notebook no es necesario tener una funcion main sin embargo en un proyecto normal de Rust es necesario tenerla.
fn main() {
println!("Hello World!");
}
main();
Hello World!
El formateo puede ser por medio del orden de las variables en corchetes
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");
Alice, this is Bob. Bob, this is Alice
O puede ser llamándolo por el nombre de la variable en los corchetes
println!("{subject} {verb} {object}",
object="the lazy dog",
subject="the quick brown fox",
verb="jumps over");
the quick brown fox jumps over the lazy dog
En rust tenemos dos grupos grandes de primiticos los: escalares y los compuestos.
let logical: bool = true;
let a_float: f64 = 1.0; // Anotacion regular especificando nombre: Tipo
let an_integer = 5i32; // Anotacion sufija especificando el tipo de dato en el valor
//O sino se especifica el tipo se pondra el default
let default_float = 3.0; // `f64`
let default_integer = 7; // `i32`
let a_bool = true;
let a_char= 'a';
// Tuplas de diferentes tipos de datos
let tuple_of_tuples = (1u8, "Hola", true);
// Arreglo de tamaño 5 y de tipo entero 32
let xs: [i32; 5] = [1, 2, 3, 4, 5];
Struct: son estructuras, similares a las que tiene el lenguaje C. Hay tres tipos de estructuras:
// Estructura basica
struct Person {
name: String,
age: u8,
}
// Estructura de tupla
struct Pair(i32, bool);
// Unit struct
struct Unit;
// A instanciar!
let Person: Person = Person { name: "Alejandra".to_string() , age: 20u8 };
let Par:Pair = Pair(1, false);
Enum: permite la creacion de un tipo que puede ser uno de muchas variantes
enum WebEvent {
// Un enum puede ser "unit-like",
PageLoad,
PageUnload,
// O tuplas
KeyPress(char),
Paste(String),
// O estructuras básicas
Click { x: i64, y: i64 },
}
let pressed = WebEvent::KeyPress('x');
let pasted = WebEvent::Paste("my text".to_owned());
let click = WebEvent::Click { x: 20, y: 80 };
let load = WebEvent::PageLoad;
let unload = WebEvent::PageUnload;
Constantes: Rust tiene dos tipos de constantes
static LANGUAGE: &str = "Rust";
const THRESHOLD: i32 = 10;
fn main() {
println!("This is {}", LANGUAGE);
println!("The threshold is {}", THRESHOLD);
}
main();
This is Rust The threshold is 10
Por defecto TODAS las variables son inmutables, para modificarlas es necesario utilizar la palabra reservada mut
fn main() {
let _immutable_binding = 1;
let mut mutable_binding = 1;
println!("Before mutation: {}", mutable_binding);
// Ok
mutable_binding += 1;
println!("After mutation: {}", mutable_binding);
// ERROR! Al intentar de modificar la variable
_immutable_binding += 1;
}
let _immutable_binding = 1; ^^^^^^^^^^^^^^^^^^ first assignment to `_immutable_binding` _immutable_binding += 1; ^^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable cannot assign twice to immutable variable `_immutable_binding` help: make this binding mutable mut _immutable_binding
Todas las variables tienen un "scope" o alcance, la vida de la variable esta delimitada por un bloque referenciado por dos corchetes {}
fn main() {
let shadowed_binding = 1;
{
println!("antes de shadowing: {}", shadowed_binding);
// Esta asignacion le hace "shadowing" a la anterior
let shadowed_binding = "abc";
println!("shadowed en bloque interior {}", shadowed_binding);
}
println!("afuera del bloque interior: {}", shadowed_binding);
let shadowed_binding = 2;
println!("shadowed en bloque exterior {}", shadowed_binding);
}
main();
antes de shadowing: 1 shadowed en bloque interior abc afuera del bloque interior: 1 shadowed en bloque exterior 2
En Rust no existe el casting implícito entre tipos de datos, pero se puede forzar explícitamente usando la palabra reservada [as]
let decimal = 65.4321_f32;
// Conversion explicita
let integer = decimal as u8;
let character = integer as char;
Tambien es posible, hacer casting a tus propios tipos de datos y estructuras por medio de las conversiones.
From
: este trait permite definir como un tipo de dato personalizado puede crearse a si mismo desde otro tipo de dato
let my_str = "hello";
let my_string = String::from(my_str);
Into
: este trait es reciproco a From
. Permite definir como crear otro tipo de dato a partir de un tipo de dato personalizado
use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
fn main() {
let int = 5;
// Try removing the type declaration
let num: Number = int.into();
println!("My number is {:?}", num);
}
main();
My number is Number { value: 5 }
Ya hemos visto muchos ejemplos de expresiones en Rust pero queremos añadir datos adicionales especiales de este lenguaje. Por ejemplo, los bloques de código cuentan como expresiones por lo tanto pueden ser asignados a una variable.
let x = 5u32;
let z = {
2 * x
};
println!("z is {:?}", z);
z is 10
if/else
¶La unica diferencia con otros lenguajes es que la condición no necesita estar entre parentesis
let n = 5;
if n < 0 {
println!("{} is negative", n);
} else if n > 0 {
println!("{} is positive", n);
} else {
println!("{} is zero", n);
};
5 is positive
loop
¶Ciclo infinito
let mut count = 0u32;
loop {
count += 1;
println!("{}", count);
if count == 5 {
println!("Okey, suficiente");
// Exit this loop
break;
}
};
1 2 3 4 5 Okey, suficiente
while
¶Parecido a un loop
pero con condicion
let mut n = 1;
while n < 10 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
// Increment counter
n += 1;
};
1 2 fizz 4 buzz fizz 7 8 fizz
for
y rangos
¶Un poco mas parecido al for
en python
let t= 5;
//for en Rust no inclusivo el t
for i in 0..t{
println!("{}",i)
};
let names = vec!["Bob", "Frank", "Ferris"];
for name in names{
println!("Hello {}", name);
};
0 1 2 3 4 Hello Bob Hello Frank Hello Ferris
match
¶Es un switch de C
let number = 5;
//probar con diferentes
println!("Dime sobre el numero {}", number);
match number {
// Comparar con solo un valor
1 => println!("Uno!"),
// Comparar con multiples valores
2 | 3 | 5 | 7 | 11 => println!("Es primo!"),
// Comprar con un rango
16..=19 => println!("Ya puedes manejar :)"),
// Default
_ => println!("Nada especial :( "),
};
Dime sobre el numero 5 Es primo!
Las funciones se crean por medio de la palabra clave fn
. Tiene como estructura fn nombre (parametros)
-> Tipo de dato de retorno
fn duplicar(num: i32) -> i32{
num*2
}
duplicar(8)
16
Son funciones atadas a objetos
struct Rectangle {
base: i32,
altura: i32,
}
impl Rectangle {
fn new(base: i32, altura: i32) -> Rectangle {
Rectangle { base: base, altura: altura }
}
//self se refiere al objeto de llama la funcion
fn area(&self) -> i32 {
let base = self.base;
let altura = self.altura;
base * altura
}
}
let rectangle = Rectangle::new(15,30);
println!("El area es {}",rectangle.area());
El area es 450
Son parecidas a una función lambda
fn main () {
fn function(i: i32) -> i32 { i + 1 }
let closure_annotated = |i: i32| -> i32 { i + 1 };
let i = 5;
println!("function: {}", function(i));
println!("closure_annotated: {}", closure_annotated(i));
}
main();
function: 6 closure_annotated: 6
Ahora veamos lo aprendido en las presentaciones en código. Como recuerdan los valores tienen un unico dueño y solo se pueden prestar o transferir (la ownership). Esto previene que los recursos sean liberados mas de una vez.
Despues de que un recurso se transfiera, el dueño previo no puede usar ese recurso.
//Recuerdan las estructuras unit type?
struct Foo(i32);
//Foo(1) le pertenece ahora a la variable X
let x = Foo(1);
// Ahora Foo(1) le pertenece a y, y x ya no tiene valor,
// esto se le conoce como MOVE de Foo(1) a y
let y = x;
// Error: x ya no tiene el valor de Foo(1), ahora eso le pertenece a y
println!("{}", x.0);
let y = x; ^ value moved here println!("{}", x.0); ^^^ value borrowed here after move let x = Foo(1); ^ move occurs because `x` has type `Foo`, which does not implement the `Copy` trait borrow of moved value: `x`
struct Foo(i32);
let x = Foo(1);
let y = x;
println!("{}", y.0);
1
Esto permite que parte de la variable se transfiera y otra parte permanezca. En este caso la variable padre no puede usarse de manera completa, solo lo que permanece.
fn main() {
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
let person = Person {
name: String::from("Alice"),
age: 20,
};
// el valor `name` se movio afuera de Person pero age al usar ref permanece
let Person { name, ref age } = person;
println!("The person's age is {}", age);
println!("The person's name is {}", name);
// Si llamo name para person saldra error porque este ya no es dueño
//println!("The person struct is {:?}", person.name);
// `person` ya no se puede usar pero `person.age` si puede usarse ya que este no se mueve
println!("The person's age from person struct is {}", person.age);
}
main();
The person's age is 20 The person's name is Alice The person's age from person struct is 20
Esta caracteristica la usamos cuando queremos acceder a datos de otra variable sin tomar propiedad de este. Esto se hace por medio de las referencias que se denotan por un &
. Es importante decir que las referencias tanto como las variables son inmutables a menos que se especifique lo contrario.
fn main() {
let s1 = String::from("hello");
//Aca len no tiene propiedad de s1 sino solo su referencia
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
// Recibe como parametros una referencia a un string
fn calculate_length(s: &String) -> usize {
s.len()
}
main();
The length of 'hello' is 5.
fn main() {
let s = String::from("hello");
change(&s);
}
//ERROR porque la referencia es inmutable
fn change(some_string: &String) {
some_string.push_str(", world");
}
main();
some_string.push_str(", world"); ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable cannot borrow `*some_string` as mutable, as it is behind a `&` reference help: consider changing this to be a mutable reference &mut String
fn main() {
let mut s = String::from("hello");
//Para hacer la variable mutable simplemente basta con la palabra clave "mut"
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
main();
Es una construcción que el compilador usa para que todos los "borrows" sean válidos. Especificamente el tiempo de vida de una variable empieza cuando esta es creada y termina cuando es destruida. Este está relacionado con los scopes.
fn main() {
let i = 3; //Lifetime de `i` empieza. ─────────────────┐
// │
{ // scope1 │
let borrow1 = &i; // borrow 1 lifetime empieza. ──┐│
// ││
println!("borrow1: {}", borrow1); // ││
} // `borrow1 lt termina. ────────────────────────────┘│
// │
// │
{ // scope 2 │
let borrow2 = &i; // `borrow2` lifetime empieza. ─┐│
// ││
println!("borrow2: {}", borrow2); // ││
} // `borrow2` lf termina. ───────────────────────────┘│
// │
} // Lifetime termina ───────────────────────────────────┘
Para crear hilos llamamos la funcion thread::spawn
y le pasamos una clousure (si no recuerdan que es esto miren la sección de Clousures) con el codigo que deseamos correr en los hilos dentro.
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| { //clousure vacio
for i in 1..10 {
//Aqui va el codigo que queramos correr en cada hilo
println!("Hola soy el hilo numero {}! ", i);
//El sleep ayuda a parar la ejecucion de un hilo momentaneamente
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("Hola soy el numero {} del hilo principal", i);
thread::sleep(Duration::from_millis(1));
}
}
main();
Hola soy el numero 1 del hilo principal Hola soy el hilo numero 1! Hola soy el hilo numero 2! Hola soy el numero 2 del hilo principal Hola soy el hilo numero 3! Hola soy el numero 3 del hilo principal Hola soy el numero 4 del hilo principal Hola soy el hilo numero 4! Hola soy el hilo numero 5!
Podemos esperar que todos los hilos terminen para hacer otra cosa por medio de las JoinHandle
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("Hola soy el hilo numero {}! ", i);
thread::sleep(Duration::from_millis(1));
}
});
//Esto permite que el hilo principal espere a que todos los hilos terminen antes de continuar
handle.join().unwrap();
for i in 1..5 {
println!("Hola soy el numero {} del hilo principal", i);
thread::sleep(Duration::from_millis(1));
}
}
main();
Hola soy el hilo numero 1! Hola soy el hilo numero 2! Hola soy el hilo numero 3! Hola soy el hilo numero 4! Hola soy el hilo numero 5! Hola soy el hilo numero 6! Hola soy el hilo numero 7! Hola soy el hilo numero 8! Hola soy el hilo numero 9! Hola soy el numero 1 del hilo principal Hola soy el numero 2 del hilo principal Hola soy el numero 3 del hilo principal Hola soy el numero 4 del hilo principal