Paz
un puntero *
y una referencia &
en Rust comparten la misma representación (ambos representan la dirección de memoria de un dato).
Sin embargo, ¿cuáles son las diferencias prácticas al escribir código?
Al migrar el código C++ a Rust, ¿se pueden reemplazar de forma segura (puntero de C++ –> puntero de óxido, referencia de C++ –> referencia de óxido)?
Francisco Gagne
Use referencias cuando pueda, use punteros cuando deba hacerlo. Si no está haciendo FFI o administración de memoria más allá de lo que el compilador puede validar, no necesita usar punteros.
Tanto las referencias como los punteros existen en dos variantes. Hay referencias compartidas. &
y referencias mutables &mut
. Hay punteros constantes *const
y mut punteros *mut
(que se asignan a punteros constantes y no constantes en C). Sin embargo, la semántica de las referencias es completamente diferente de la semántica de los punteros.
Las referencias son genéricas sobre un tipo y durante toda la vida. Las referencias compartidas se escriben &'a T
en forma larga (donde 'a
y T
son parámetros). El parámetro de vida puede ser omitido en muchas situaciones El compilador utiliza el parámetro de duración para garantizar que una referencia no dure más de lo que es válido el préstamo.
Los punteros no tienen ningún parámetro de duración. Por lo tanto, el compilador no puede verificar que un puntero en particular sea válido para usar. Es por eso que se considera desreferenciar un puntero. unsafe
.
Cuando crea una referencia compartida a un objeto, ese se congela el objeto (es decir, el objeto se vuelve inmutable mientras exista la referencia compartida), a menos que el objeto use alguna forma de mutabilidad interior (por ejemplo, usando Cell
, RefCell
, Mutex
o RwLock
). Sin embargo, cuando tiene un puntero constante a un objeto, ese objeto aún puede cambiar mientras el puntero está activo.
Cuando tiene una referencia mutable a un objeto, está garantizado tener acceso exclusivo a ese objeto a través de esta referencia. Cualquier otra forma de acceder al objeto está deshabilitada temporalmente o es imposible de lograr. Por ejemplo:
let mut x = 0;
{
let y = &mut x;
let z = &mut x; // ERROR: x is already borrowed mutably
*y = 1; // OK
x = 2; // ERROR: x is borrowed
}
x = 3; // OK, y went out of scope
Los punteros mut no tienen tal garantía.
Una referencia no puede ser nula (al igual que las referencias de C++). Un puntero puede ser nulo.
Los punteros pueden contener cualquier valor numérico que pueda caber en un usize
. Inicializar un puntero no es unsafe
; solo desreferenciarlo es. Por otro lado, se considera que producir una referencia inválida comportamiento indefinidoincluso si nunca lo desreferencias.
Si tienes un *const T
puedes lanzarlo libremente a un *const U
o a un *mut T
usando as
. No puedes hacer eso con referencias. Sin embargo, puede emitir una referencia a un puntero usando as
y puede “actualizar” un puntero a una referencia eliminando la referencia del puntero (que, de nuevo, es unsafe
) y luego tomar prestado el lugar usando &
o &mut
. Por ejemplo:
use std::ffi::OsStr;
use std::path::Path;
pub fn os_str_to_path(s: &OsStr) -> &Path {
unsafe { &*(s as *const OsStr as *const Path) }
}
En C++, las referencias son “punteros desreferenciados automáticamente”. En Rust, a menudo aún necesita desreferenciar las referencias explícitamente. La excepción es cuando usas el .
operador: si el lado izquierdo es una referencia, el compilador lo desreferenciará automáticamente (¡recursivamente si es necesario!). Los punteros, sin embargo, no se desreferencian automáticamente. Esto significa que si desea desreferenciar y acceder a un campo o método, debe escribir (*pointer).field
o (*pointer).method()
. No hay ->
operador en Rust.
Las referencias a Rust son solo un puntero, pero el compilador las dota de semántica prestada. Cuando toma una referencia inmutable a un objeto, el compilador se asegura de que no pueda modificar ese objeto hasta que desaparezca la referencia derivada.
-
¿Podemos usar un puntero en lugar de introducir una referencia en Rust, es decir, dar el puntero con semántica prestada?
– EvanL00
19 de enero de 2021 a las 1:02
-
Puede hacer cualquier cosa para la que pueda escribir código, incluido eso. Sin embargo, no todo es una buena idea.
– NovaDenizen
23 de enero de 2021 a las 4:57
En términos generales, puede hacer más con referencias en Rust que en C++, y los punteros sin procesar en C++ se usan para mucho más que en Rust. Si está utilizando C ++ moderno con punteros en su mayoría inteligentes, a menudo se pueden traducir directamente (por ejemplo,
std::unique_ptr
->Box
,std::shared_ptr
->Arc
, aunque no todos son tan sencillos). Si su C ++ tiene muchos punteros C, deberá evaluar cómo se están utilizando. Una gran cantidad de código de C++ usa punteros sin procesar innecesariamente donde las referencias o los punteros inteligentes funcionarían mejor.– trento
6 de junio de 2020 a las 14:10
Probablemente tendrá muchas dificultades para hacer un puerto directo de un diseño de C++ a Rust. Es mejor mantener el diseño antiguo en su mente y esbozar un diseño completamente nuevo usando Rust, en lugar de tratar de convertir todas las piezas función por función y estructura de datos por estructura de datos y juntarlas todas en el fin. Esto se duplica si el diseño anterior contiene estructuras de datos cíclicas o herencia de clases.
– trento
6 de junio de 2020 a las 14:11