Translate

viernes, 11 de agosto de 2023

Structs en Rust

 


Al igual que C y C++, Rust tiene soporte para estructuras:


struct Person {

    name: String,

    age: u8,

}


fn main() {

    let mut peter = Person {

        name: String::from("Peter"),

        age: 27,

    };

    println!("{} is {} years old", peter.name, peter.age);

    

    peter.age = 28;

    println!("{} is {} years old", peter.name, peter.age);

    

    let jackie = Person {

        name: String::from("Jackie"),

        ..peter

    };

    println!("{} is {} years old", jackie.name, jackie.age);

}

Las estructuras funcionan como en C o C++.

  • Al igual que en C++, ya diferencia de C, no se necesita typedef para definir un tipo.
  • A diferencia de C++, no hay herencia entre estructuras.
  • Los métodos se definen en un bloque impl, que veremos más adelante.
  • Este puede ser un buen momento para que la gente sepa que hay diferentes tipos de estructuras.
  • Estructuras de tamaño cero, por ejemplo, struct Foo; podría usarse al implementar un rasgo en algún tipo pero no tiene ningún dato que desee almacenar en el valor mismo.
  • Ls estructuras Tuple, que se utilizan cuando los nombres de los campos no son importantes.
  • La sintaxis ..peter nos permite copiar la mayoría de los campos de la estructura anterior sin tener que escribirlos todos explícitamente. Siempre debe ser el último elemento.

¿Cómo promover experiencias de datos?

 

 Me llego este mail de google cloud y lo quiero compartir con ustedes: 

jueves, 10 de agosto de 2023

Tiempos de vida en estructuras de datos


 Si un tipo de datos almacena datos prestados, debe anotarse con un tiempo de vida:

#[derive(Debug)]

struct Highlight<'doc>(&'doc str);


fn erase(text: String) {

    println!("Bye {text}!");

}


fn main() {

    let text = String::from("The quick brown fox jumps over the lazy dog.");

    let fox = Highlight(&text[4..19]);

    let dog = Highlight(&text[35..43]);

    // erase(text);

    println!("{fox:?}");

    println!("{dog:?}");

}


Si corremos esto: 

$ cargo run

   Compiling hello_cargo v0.1.0 (/home/emanuel/Projects/rust/hello_cargo)

warning: function `erase` is never used

 --> src/main.rs:7:4

  |

7 | fn erase(text: String) {

  |    ^^^^^

  |

  = note: `#[warn(dead_code)]` on by default


warning: `hello_cargo` (bin "hello_cargo") generated 1 warning

    Finished dev [unoptimized + debuginfo] target(s) in 1.80s

     Running `target/debug/hello_cargo`

Highlight("quick brown fox")

Highlight("lazy dog")

Pero si descomentamos esta linea // erase(text); y corremos:

$ cargo run

   Compiling hello_cargo v0.1.0 

error[E0505]: cannot move out of `text` because it is borrowed

  --> src/main.rs:19:11

   |

15 |     let fox = Highlight(&text[4..19]);

   |                          ---- borrow of `text` occurs here

...

19 |     erase(text);

   |           ^^^^ move out of `text` occurs here

20 |

21 |     println!("{fox:?}");

   |                --- borrow later used here


For more information about this error, try `rustc --explain E0505`.

error: could not compile `hello_cargo` due to previous error

  • En el ejemplo anterior, la anotación en Highlight impone que los datos subyacentes al &str contenido vivan al menos tanto tiempo como cualquier instancia de Highlight que use esos datos.
  • Si el texto se consume antes del final de la vida útil del fox (o del dog), el comprobador de préstamos arroja un error.
  • Los tipos con datos prestados obligan a los usuarios a conservar los datos originales. Esto puede ser útil para crear vistas ligeras, pero generalmente las hace un poco más difíciles de usar.
  • Cuando sea posible, haga que las estructuras de datos sean propietarias de sus datos directamente.
  • Algunas estructuras con múltiples referencias internas pueden tener más de una anotación de por vida. Esto puede ser necesario si existe la necesidad de describir las relaciones de duración entre las propias referencias, además de la duración de la estructura en sí. Esos son casos de uso muy avanzados.

Simplificando la Construcción de Imágenes con Paketo


Paketo es un proyecto de código abierto que tiene como objetivo simplificar el proceso de construcción y empaquetado de imágenes de contenedor. Ofrece un enfoque más moderno y eficiente en comparación con los enfoques tradicionales de Dockerfile. Paketo se basa en principios de mejores prácticas y utiliza lenguajes de construcción específicos para diferentes lenguajes de programación, lo que facilita la creación de imágenes de contenedor optimizadas y seguras.

  • Paquetes de Lenguaje Específico: Paketo proporciona paquetes de lenguaje específico que contienen todos los elementos necesarios para construir y ejecutar una aplicación en contenedor. Esto elimina la necesidad de configuraciones largas en Dockerfile y asegura que las imágenes sean consistentes y eficientes.
  • Detección Automática de Dependencias: Paketo tiene la capacidad de detectar automáticamente las dependencias de tu aplicación, como bibliotecas y herramientas, y agregarlas a la imagen de contenedor. Esto simplifica la gestión de dependencias y reduce la posibilidad de errores.
  • Actualizaciones de Seguridad Automáticas: Paketo ofrece actualizaciones automáticas de seguridad para las dependencias y capas de la imagen. Esto garantiza que las imágenes de contenedor estén siempre actualizadas y protegidas contra vulnerabilidades conocidas.
  • Compatibilidad Multiplataforma: Las imágenes de contenedor construidas con Paketo son compatibles con múltiples plataformas, lo que permite la implementación en diversos entornos sin preocuparse por la compatibilidad.
  • Configuración Desacoplada: A diferencia de los Dockerfiles monolíticos, Paketo promueve la configuración desacoplada mediante capas, lo que facilita la reutilización y el mantenimiento del código.


Instalación de Paketo: La instalación de Paketo es sencilla y depende del lenguaje de programación que utilices. Sigue las instrucciones en la documentación oficial para instalar la CLI de Paketo y los paquetes necesarios.

Construcción de Imágenes: Para construir una imagen de contenedor con Paketo, dirígete al directorio de tu proyecto y utiliza el comando correspondiente al paquete de lenguaje que estás utilizando. Por ejemplo, para Java:


pack build my-java-app --builder paketobuildpacks/builder:base


Este comando construirá una imagen de contenedor para tu aplicación Java.

Despliegue y Distribución: Una vez construida la imagen, puedes distribuirla en tu plataforma de elección, como Kubernetes o Docker Swarm. Las imágenes de contenedor construidas con Paketo son portables y compatibles con una variedad de entornos.

Paketo ha revolucionado la forma en que empaquetamos y distribuimos aplicaciones en contenedores. Su enfoque en paquetes de lenguaje específico, detección automática de dependencias y actualizaciones de seguridad automáticas simplifican significativamente el proceso de construcción y mantenimiento de imágenes de contenedor. Si buscas una forma más eficiente y segura de empaquetar tus aplicaciones, Paketo es una herramienta que definitivamente vale la pena explorar. Su impacto en la productividad y la seguridad en el desarrollo y la implementación de software es innegable.

Dejo link : https://paketo.io/



Duración de las llamadas a funciones en Rust

 


Además de tomar prestados sus argumentos, una función puede devolver un valor prestado:

#[derive(Debug)]

struct Point(i32, i32);


fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {

    if p1.0 < p2.0 { p1 } else { p2 }

}


fn main() {

    let p1: Point = Point(10, 10);

    let p2: Point = Point(20, 20);

    let p3: &Point = left_most(&p1, &p2);

    println!("left-most point: {:?}", p3);

}

  • 'a es un parámetro genérico, lo infiere el compilador.
  • Los tiempos de vida comienzan con ' y 'a es un nombre predeterminado típico.
  • Lea &'a Point como “un Punto prestado que es válido por lo menos durante el tiempo de vida a”.
  • La parte al menos es importante cuando los parámetros están en diferentes ámbitos.

En el ejemplo anterior, intente lo siguiente:

Mueva la declaración de p2 y p3 a un nuevo ámbito ({ ... }), lo que da como resultado el siguiente código:

#[derive(Debug)]

struct Point(i32, i32);


fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {

    if p1.0 < p2.0 { p1 } else { p2 }

}


fn main() {

    let p1: Point = Point(10, 10);

    let p3: &Point;

    {

        let p2: Point = Point(20, 20);

        p3 = left_most(&p1, &p2);

    }

    println!("left-most point: {:?}", p3);

}


Esto no compila ya que p3 sobrevive a p2.

Restablezca el espacio de trabajo y cambie la firma de la función a fn left_most<'a, 'b>(p1: &'a Point, p2: &'a Point) -> &'b Point. Esto no se compilará porque la relación entre las vidas 'a y 'b no está clara.

Otra forma de explicarlo:

  • Una función toma prestadas dos referencias a dos valores y la función devuelve otra referencia.
  • Debe haber venido de una de esas dos entradas (o de una variable global).
  • ¿Cuál es? El compilador necesita saber, por lo que en el sitio de la llamada, la referencia devuelta no se usa por más tiempo que una variable de donde proviene la referencia.

miércoles, 9 de agosto de 2023

Veamos Pedestal y Clojure


Clojure es un lenguaje de programación funcional y dinámico que se ejecuta en la máquina virtual de Java (JVM). Diseñado para ser simple, eficiente y expresivo, Clojure se basa en los principios de la programación funcional y proporciona herramientas para manejar la concurrencia y la inmutabilidad de manera efectiva. Su sintaxis concisa y su énfasis en la inmutabilidad lo convierten en una excelente elección para construir aplicaciones robustas y escalables.

Pedestal es un framework web desarrollado en Clojure que se enfoca en la construcción de aplicaciones web escalables y de alto rendimiento. A diferencia de algunos otros frameworks web, Pedestal no se basa en el paradigma de controladores y vistas, sino que se centra en la manipulación de datos y el flujo de información. Esto lo convierte en una opción ideal para aplicaciones modernas que requieren manejo eficiente de datos en tiempo real.

Las caracteristicas claves son: 

  • Modelo de Manejo de Datos: Pedestal se basa en el modelo de manejo de datos (data-driven) en lugar del enfoque tradicional de controladores y vistas. Esto permite una manipulación más eficiente y coherente de los datos a medida que fluyen a través de la aplicación.
  • Componentes Reutilizables: El framework fomenta la creación de componentes reutilizables que pueden ser ensamblados fácilmente para construir aplicaciones complejas. Esto mejora la modularidad y el mantenimiento del código.
  • Enfoque Asincrónico: Pedestal está diseñado para manejar concurrencia y operaciones asincrónicas de manera efectiva. Esto es esencial para aplicaciones que necesitan manejar múltiples solicitudes y actualizaciones en tiempo real.
  • Rendimiento Optimizado: Gracias a su enfoque asincrónico y a la optimización para el manejo eficiente de datos, Pedestal es capaz de ofrecer un alto rendimiento incluso en situaciones de alta carga.


La integración de Pedestal con Clojure es perfectamente natural, ya que ambos comparten la misma plataforma de ejecución (JVM) y una filosofía similar centrada en la simplicidad y la eficiencia. Al aprovechar las capacidades funcionales de Clojure, Pedestal puede manejar de manera elegante la manipulación de datos y el flujo de información en las aplicaciones web.


Vamos a hacer un hola mundo, para ello vamos a crear el proyecto con Leiningen : 


lein new pedestal-app hello-world


Esto creará una estructura de proyecto básica en el directorio hello-world.


Ejecutamos :

lein run

El servidor Pedestal se ejecutará en el puerto 8080 por defecto. Abre tu navegador web y navega a http://localhost:8080. Deberías ver el mensaje "¡Hola, Mundo desde Pedestal y Clojure!" en la página.

Dejo link: https://github.com/pedestal/pedestal

sábado, 5 de agosto de 2023

Préstamos compartidos y únicos

 Rust impone restricciones en las formas en que puede tomar prestados valores:

  • Puede tener uno o más valores &T en un momento dado, o
  • Puede tener exactamente un valor de &mut T.
Por ejemplo: 

fn main() {
    let mut a: i32 = 10;
    let b: &i32 = &a;

    {
        let c: &mut i32 = &mut a;
        *c = 20;
    }

    println!("a: {a}");
    println!("b: {b}");
}

Si tratamos de correr este programa: 

$ cargo run
   Compiling hello_cargo v0.1.0 
error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable
  --> src/main.rs:6:27
   |
3  |     let b: &i32 = &a;
   |                   -- immutable borrow occurs here
...
6  |         let c: &mut i32 = &mut a;
   |                           ^^^^^^ mutable borrow occurs here
...
11 |     println!("b: {b}");
   |                   - immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `hello_cargo` due to previous error

El código anterior no compila porque a se toma prestado como mutable (a través de c) e inmutable (a través de b) al mismo tiempo.
¡

miércoles, 2 de agosto de 2023

Borrowing en Rust


En lugar de transferir la propiedad al llamar a una función, puede dejar que una función tome prestado el valor:


#[derive(Debug)]

struct Point(i32, i32);


fn add(p1: &Point, p2: &Point) -> Point {

    Point(p1.0 + p2.0, p1.1 + p2.1)

}


fn main() {

    let p1 = Point(3, 4);

    let p2 = Point(10, 20);

    let p3 = add(&p1, &p2);

    println!("{p1:?} + {p2:?} = {p3:?}");

}


  • La función de add toma prestados dos puntos y devuelve un nuevo punto.
  • La persona que llama conserva la propiedad de las entradas.
  • El compilador de Rust puede realizar la optimización del valor de retorno (RVO).
  • En C++, la elisión de copia debe definirse en la especificación del lenguaje porque los constructores pueden tener efectos secundarios. En Rust, esto no es un problema en absoluto. Si RVO no sucedió, Rust siempre realizará una copia memcpy simple y eficiente.

viernes, 28 de julio de 2023

Copia y Clonación en Rust


Si bien la semántica de movimiento es la predeterminada, ciertos tipos se copian de forma predeterminada:


fn main() {

    let x = 42;

    let y = x;

    println!("x: {x}");

    println!("y: {y}");

}


Estos tipos implementan el rasgo Copy.

Puede optar por que tus propios tipos implementen la semántica de Copy:


#[derive(Copy, Clone, Debug)]

struct Point(i32, i32);


fn main() {

    let p1 = Point(3, 4);

    let p2 = p1;

    println!("p1: {p1:?}");

    println!("p2: {p2:?}");

}


  • Después de la asignación, tanto p1 como p2 poseen sus propios datos.
  • También podemos usar p1.clone() para copiar explícitamente los datos.


Copiar y clonar no es lo mismo:

  • Copiar se refiere a copias bit a bit de regiones de memoria y no funciona en objetos arbitrarios.
  • La copia no permite una lógica personalizada (a diferencia de los constructores de copias en C++).
  • La clonación es una operación más general y también permite un comportamiento personalizado al implementar el rasgo Clonar.
  • La copia no funciona en los tipos que implementan el rasgo Clone.

En el ejemplo anterior, intente lo siguiente:

  • Agregue un campo de cadena a la estructura Point. No se compilará porque String no es un tipo que implemente copia.
  • Quite Copiar del atributo de derivación. ¡El error del compilador ahora está en el println! para p1.

Derivar es una forma de generar código en Rust en tiempo de compilación. En este caso, se generan las implementaciones predeterminadas de los rasgos Copiar y Clonar.

lunes, 24 de julio de 2023

Movimientos en llamadas a funciones


Cuando pasa un valor a una función, el valor se asigna al parámetro de función. Esto transfiere la propiedad:


fn say_hello(name: String) {

    println!("Hello {name}")

}


fn main() {

    let name = String::from("Alice");

    say_hello(name);

    say_hello(name);

}

Si ejecutamos este programa va a lanzar un error : 

$ cargo run

   Compiling hello_cargo v0.1.0 

error[E0382]: use of moved value: `name`

  --> src/main.rs:15:15

   |

11 |     let name = String::from("Alice");

   |         ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait

12 |

13 |     say_hello(name);

   |               ---- value moved here

14 |

15 |     say_hello(name);

   |               ^^^^ value used here after move


For more information about this error, try `rustc --explain E0382`.

error: could not compile `hello_cargo` due to previous error

Pero que paso? Con la primera llamada a say_hello, main renuncia a la propiedad del nombre. Posteriormente, el nombre ya no se puede usar dentro de main.

La memoria de almacenamiento dinámico asignada para el nombre se liberará al final de la función say_hello.

main puede conservar la propiedad si pasa nombre como referencia (&nombre) y si say_hello acepta una referencia como parámetro:


fn say_hello(name: &String) {

    println!("Hello {name}")

}


fn main() {

    let name = String::from("Alice");

    say_hello(&name);

    say_hello(&name);

}


Alternativamente, main puede pasar un clon de name en la primera llamada (name.clone()). Así:


fn say_hello(name: String) {

    println!("Hello {name}")

}


fn main() {

    let name = String::from("Alice");

    say_hello(name.clone());

    say_hello(name.clone());

}

Rust hace que sea más difícil que C ++ crear copias sin darse cuenta al hacer que la semántica de movimiento sea la predeterminada y al obligar a los programadores a hacer clones explícitos.

martes, 18 de julio de 2023

Una lista clasificada de increíbles bibliotecas y herramientas de open source de Scala.


Encontre una lista de 380 proyectos open source de scala con un total de 370 000 estrellas agrupadas en 23 categorías. Todos los proyectos se clasifican según un puntaje de calidad del proyecto, que se calcula en función de varias métricas recopiladas automáticamente de GitHub y diferentes administradores de paquetes. 

Dejo link: https://github.com/stkeky/best-of-scala

El concepto de Ownership en Rust


En el post anterior dijimos "Rust logra esto modelando la propiedad o ownership explícitamente." Peeero no explique que es esto de la propiedad o ownership. 

Todos los enlaces de variables tienen un alcance donde son válidos y es un error usar una variable fuera de su alcance, por ejemplo :

struct Point(i32, i32);

fn main() {

    {

        let p = Point(3, 4);

        println!("x: {}", p.0);

    }

    println!("y: {}", p.1);

}

Si corremos este programa: 

$ cargo run

   Compiling hello_cargo v0.1.0 

error[E0425]: cannot find value `p` in this scope

 --> src/main.rs:8:23

  |

8 |     println!("y: {}", p.1);

  |                       ^ not found in this scope


For more information about this error, try `rustc --explain E0425`.

error: could not compile `hello_cargo` due to previous error


  • Al final del alcance, la variable se elimina y los datos se liberan.
  • Un destructor puede correr aquí para liberar recursos.
  • Decimos que la variable posee el valor.
Una asignación transferirá la propiedad entre las variables:

fn main() {
    let s1: String = String::from("Hello!");
    let s2: String = s1;
    println!("s2: {s2}");
    // println!("s1: {s1}");
}

Si corremos este programa funcionará : 

$ cargo run
   Compiling hello_cargo v0.1.0 
    Finished dev [unoptimized + debuginfo] target(s) in 0.40s
     Running `target/debug/hello_cargo`
s2: Hello!

Pero si descomentamos esta linea // println!("s1: {s1}"); . Nos lanzará un error: 

$ cargo run
   Compiling hello_cargo v0.1.0 
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:20
  |
2 |     let s1: String = String::from("Hello!");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2: String = s1;
  |                      -- value moved here
4 |     println!("s2: {s2}");
5 |     println!("s1: {s1}");
  |                    ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.
error: could not compile `hello_cargo` due to previous error

Que paso?
  • La asignación de s1 a s2 transfiere la propiedad.
  • Los datos se movieron de s1 y ya no se puede acceder a s1.
  • Cuando s1 sale del alcance, no pasa nada: no tiene propiedad.
  • Cuando s2 sale del alcance, los datos de la cadena se liberan.
  • Siempre hay exactamente un enlace de variable que posee un valor.
Esto es lo opuesto a los valores predeterminados en C++, que se copia por valor a menos que use std::move (¡y el constructor de movimiento o copia está definido!).

En Rust, la clonación de valores son explícitos (usando clon).


lunes, 17 de julio de 2023

Mover Strings en Rust

 


fn main() {

    let s1: String = String::from("Rust");

    let s2: String = s1;

}

Los datos del heap de s1 se reutilizan para s2.

Cuando s1 sale del alcance, no sucede nada (se ha movido).

Antes de mover a s2:



Después de mover a s2:


viernes, 14 de julio de 2023

Comparación de Gestión de memoria con Rust


Siguiendo con el post anterior. Una comparación aproximada de las técnicas de administración de memoria.

Ventajas de las diferentes técnicas de gestión de la memoria

  • Manual como C:
    • Sin sobrecarga de tiempo de ejecución.
  • Automático como Java:
    • Completamente automático.
    • Seguro y correcto.
  • Basado en el alcance como C++:
    • Parcialmente automático.
    • Sin sobrecarga de tiempo de ejecución.
  • Basado en el alcance aplicado por el compilador como Rust:
    • Aplicado por el compilador.
    • Sin sobrecarga de tiempo de ejecución.
    • Seguro y correcto.

Contras de las diferentes técnicas de gestión de memoria

  • Manual como C:
    • Use-After-Free
    • Liberaciones dobles.
    • Pérdidas de memoria en caso de error.
  • Automático como Java:
    • Las pausas del recolector de basura.
    • Retrasos en la liberación de memoria.
  • Basado en el alcance como C++:
    • Complejo, opt-in por el programador.
    • Potencial Use-After-Free
  • Reforzado por el compilador y basado en el alcance como Rust:
    • Cierta complejidad inicial.
    • Puede rechazar programas válidos.
Use-After-Free (UAF) es una vulnerabilidad relacionada con el uso incorrecto de la memoria dinámica durante la operación del programa. Si después de liberar una ubicación de memoria, un programa no borra el puntero a esa memoria, un atacante puede usar el error para piratear el programa.

jueves, 13 de julio de 2023

Manejo de memoria en Rust

 


Tradicionalmente, los Lenguajes se han dividido en dos grandes categorías:

  • Control total a través de la gestión manual de la memoria: C, C++, Pascal, …
  • Seguridad total a través de la gestión automática de la memoria en tiempo de ejecución: Java, Python, Go, Haskell, …

Rust ofrece una nueva mezcla:

Control total y seguridad a través de la aplicación en tiempo de compilación de la gestión correcta de la memoria.

Lo hace con un concepto de propiedad explícito.

Primero, actualicemos cómo funciona la administración de memoria.

Stack: 

  • Área continua de memoria para variables locales.
  • Los valores tienen tamaños fijos conocidos en tiempo de compilación.
  • Extremadamente rápido: simplemente mueva un puntero de pila.
  • Fácil de administrar: sigue las llamadas de función.
  • Gran recuerdo localidad.

Heap o monticulo : 

  • almacenamiento de valores fuera de las llamadas a funciones.
  • Los valores tienen tamaños dinámicos determinados en tiempo de ejecución.
  • Ligeramente más lento que la pila: se necesita algo de contabilidad.
  • No hay garantía de localidad de memoria.

La creación de una cadena coloca datos de tamaño fijo en la pila o stack y datos de tamaño dinámico en el monticulo o Heap :

fn main() {
    let s1 = String::from("Hello");
}



Un String está respaldado por un Vec, por lo que tiene una capacidad y longitud y puede crecer si es mutable a través de la reasignación en el Heap.

Podemos inspeccionar el diseño de la memoria con código inseguro. Sin embargo, debe señalar que esto es legítimamente inseguro.

fn main() {
    let mut s1 = String::from("Hello");
    s1.push(' ');
    s1.push_str("world");
    
    unsafe {
        let (capacity, ptr, len): (usize, usize, usize) = std::mem::transmute(s1);
        println!("ptr = {ptr:#x}, len = {len}, capacity = {capacity}");
    }
}

Veamos como son las formas que tienen de gestionar la memoria los lenguajes: 

Gestión de memoria manual

Usted mismo asigna y desasigna la memoria del heap. Se llama Gestión de memoria manual

Si no se hace con cuidado, esto puede provocar bloqueos, errores, vulnerabilidades de seguridad y pérdidas de memoria.

Ejemplo C, debe llamar a  free cada vez que asigno un puntero con malloc:

void foo(size_t n) {

    int* int_array = malloc(n * sizeof(int));

    //

    // ... lots of code

    //

    free(int_array);

}

Si la función termina entre malloc y free: el puntero se pierde y no podemos desasignar la memoria. Esto es un Memory leaked. 

Administración de memoria basada en alcance o ámbito

Los constructores y destructores te permiten engancharte a la vida útil de un objeto.

Al envolver un puntero en un objeto, puede liberar memoria cuando se destruye el objeto. El compilador garantiza que esto suceda, incluso si se genera una excepción.

Esto a menudo se denomina adquisición de recursos es inicialización (RAII) y le brinda indicadores inteligentes.

Un ejemplo de C++

void say_hello(std::unique_ptr<Person> person) {

  std::cout << "Hello " << person->name << std::endl;

}

  • El objeto std::unique_ptr se asigna en la pila y apunta a la memoria asignada en el heap.
  • Al final de say_hello, se ejecutará el destructor std::unique_ptr.
  • El destructor libera el objeto Person al que apunta.
Los constructores de movimientos especiales se usan cuando se pasa la propiedad a una función:

std::unique_ptr<Person> person = find_person("Carla");
say_hello(std::move(person));

Gestión automática de memoria

Una alternativa a la gestión de memoria manual y basada en el ámbito es la gestión de memoria automática:

  • El programador nunca asigna o desasigna memoria explícitamente.
  • Un recolector de basura encuentra memoria no utilizada y la desasigna para el programador.

Ejemplo Java, el objeto persona no se desasigna después de que sayHello devuelve:

void sayHello(Person person) {
  System.out.println("Hello " + person.getName());
}

Gestión de memoria en Rust

La gestión de la memoria en Rust es una combinación:

  • Seguro y correcto como Java, pero sin un recolector de basura.
  • Según la abstracción (o combinación de abstracciones) que elija, puede ser un único puntero único, referencia contada o atómicamente referencia contada.
  • Basado en el alcance como C++, pero el compilador impone una adherencia total.
  • Un usuario de Rust puede elegir la abstracción correcta para la situación, algunos incluso no tienen costo en tiempo de ejecución como C.
Rust logra esto modelando la propiedad o ownership explícitamente.