Los punteros son básicamente variables que almacenan la dirección de otra variable. Mientras que los punteros inteligentes son tipos de datos que se comportan como un puntero tradicional con capacidades adicionales, como la gestión automática de memoria o la comprobación de límites.
Lo que hace que los punteros inteligentes sean diferentes de los punteros normales son:
- Son estructuras y contienen metadatos y capacidades adicionales.
- No toman prestados los datos, sino que los poseen.
Lo que hace que los punteros inteligentes sean diferentes de las estructuras: –
- Implementan los rasgos Deref y Drop .
El rasgo Deref permite que una instancia de una estructura de puntero inteligente se comporte como una referencia para que el código que funciona con punteros también pueda funcionar con punteros inteligentes.
El rasgo de caída nos permite personalizar el código que debe ejecutarse cuando una instancia del puntero inteligente queda fuera del alcance.
Algunos de los punteros inteligentes son: –
- Box<T> para asignar valores en el montón
- Rc<T> un tipo de conteo de referencia que permite la propiedad múltiple
Caja<T>
Box nos permite almacenar datos en el montón contrario al esquema predeterminado de Rust para almacenar como una pila.
La caja se utiliza básicamente:
- Para asignación dinámica de memoria para variables.
- Cuando hay muchos datos que necesitamos transferir y no queremos que se copien.
Creemos una caja para almacenar el valor i32 en un montón
Rust
fn main() { let num = Box::new(4); println!("num = {}", num); }
Producción
num = 4
Usando Box<T> para el tipo recursivo
Usaremos la lista de contras para crear una lista de valores. cons list toma valores, el primero es el valor actual y el otro es el siguiente valor, realiza una llamada recursiva a la función cons para generar una lista, donde la condición base para la llamada recursiva es Nil.
Rust
enum List { Cons(i32, List), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil))); }
No se compilará porque el tamaño de la variable Lista no se puede determinar antes de la compilación.
Producción:
rustc -o main main.rs error[E0072]: recursive type `List` has infinite size --> main.rs:1:1 | 1 | enum List { | ^^^^^^^^^ recursive type has infinite size 2 | Cons(i32, List), | ---- recursive without indirection | = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable error[E0391]: cycle detected when processing `List` --> main.rs:1:1 | 1 | enum List { | ^^^^^^^^^ | = note: ...which again requires processing `List`, completing the cycle = note: cycle used when computing dropck types for `Canonical { max_universe: U0, variables: [], value: ParamEnvAnd { param_env: ParamEnv { caller_bounds: [], reveal: UserFacing, def_id: None }, value: List } }`
Hay un error que indica que la Lista tiene un tamaño infinito porque el compilador no puede determinar el tamaño de la Lista durante la compilación. Por lo tanto, usaremos un puntero a la lista en lugar de la lista en sí para solucionar este error. Dado que el tamaño del puntero se fija independientemente del tipo de datos al que apunta, el compilador puede determinar su tamaño durante la compilación. Veamos esta implementación usando Box<T>
Rust
#[derive(Debug)] enum List { Cons(i32, Box<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); println!("{:?}",list) }
Producción:
Cons(1, Cons(2, Cons(3, Nil)))
Definiendo nuestro propio puntero inteligente:
Rust
struct CustomPointer<T>(T); impl<T> CustomPointer<T> { fn new(x: T) -> CustomPointer<T> { CustomPointer(x) } }
Aquí, hemos definido un puntero inteligente personalizado como una estructura de tupla con un valor. Hemos definido un nuevo método que se llama cuando se crea una instancia del puntero inteligente, que devuelve un puntero inteligente personalizado.
Usando el rasgo de desreferencia en nuestro puntero inteligente personalizado:
Al igual que un puntero ordinario, podemos obtener el valor de la caja usando el operador ‘*’.
Rust
struct CustomPointer<T>(T); impl<T> CustomPointer<T> { fn new(x: T) -> CustomPointer<T> { CustomPointer(x) } } //implementing deref trait use std::ops::Deref; impl<T> Deref for CustomPointer<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } fn main() { let x = 5; let y = CustomPointer::new(x); println!("{}",x==5); println!("{}",*y==5); }
Producción:
true true
Usando el rasgo Drop en nuestro puntero inteligente:
Cada vez que la instancia del puntero inteligente queda fuera del alcance, se llama a la función de eliminación.
Rust
// defining custom smart pointer struct CustomPointer <T>(T); impl<T> CustomPointer<T> { fn new(x: T) -> CustomPointer<T> { CustomPointer(x) } } use std::ops::Deref; // for implementing deref trait impl<T> Deref for CustomPointer<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } // for implementing drop trait impl <T> Drop for CustomPointer<T> { fn drop(&mut self) { println!("Dropping CustomtPointer with data"); } } fn main() { let x = 5; let y = CustomPointer::new(x); println!("{}",x==5); println!("{}",*y==5); }
Producción:
true true Dropping CustomtPointer with data