Saltearse al contenido

Referencias

Move tiene dos tipos de referencias: inmutables & y mutables &mut. Las referencias inmutables son de solo lectura y no pueden modificar el valor subyacente (o cualquiera de sus campos). Las referencias mutables permiten modificaciones a través de una escritura a través de esa referencia. El sistema de tipos de Move aplica una disciplina de propiedad que previene errores de referencia.

Para más detalles sobre las reglas de referencias, ver Structs y Recursos

Move proporciona operadores para crear y extender referencias, así como convertir una referencia mutable en una inmutable. Aquí y en otros lugares, usamos la notación e: T para “la expresión e tiene tipo T”.

SintaxisTipoDescripción
&e&T donde e: T y T es un tipo no-referenciaCrear una referencia inmutable a e
&mut e&mut T donde e: T y T es un tipo no-referenciaCrear una referencia mutable a e.
&e.f&T donde e.f: TCrear una referencia inmutable al campo f del struct e.
&mut e.f&mut T donde e.f: TCrear una referencia mutable al campo f del struct e.
freeze(e)&T donde e: &mut TConvertir la referencia mutable e en una referencia inmutable.

Los operadores &e.f y &mut e.f pueden usarse tanto para crear una nueva referencia en un struct como para extender una referencia existente:

script {
fun example() {
let s = S { f: 10 };
let f_ref1: &u64 = &s.f; // funciona
let s_ref: &S = &s;
let f_ref2: &u64 = &s_ref.f; // también funciona
}
}

Una expresión de referencia con múltiples campos funciona siempre que ambos structs estén en el mismo módulo:

module 0x42::example {
struct A { b: B }
struct B { c : u64 }
fun f(a: &A): &u64 {
&a.b.c
}
}

Finalmente, nota que las referencias a referencias no están permitidas:

script {
fun example() {
let x = 7;
let y: &u64 = &x;
let z: &&u64 = &y; // no compilará
}
}

Lectura y Escritura a Través de Referencias

Sección titulada «Lectura y Escritura a Través de Referencias»

Tanto las referencias mutables como las inmutables pueden leerse para producir una copia del valor referenciado.

Solo las referencias mutables pueden escribirse. Una escritura *x = v descarta el valor previamente almacenado en x y lo actualiza con v.

Ambas operaciones usan la sintaxis * similar a C. Sin embargo, nota que una lectura es una expresión, mientras que una escritura es una mutación que debe ocurrir en el lado izquierdo de un igual.

SintaxisTipoDescripción
*eT donde e es &T o &mut TLeer el valor apuntado por e
*e1 = e2() donde e1: &mut T y e2: TActualizar el valor en e1 con e2.

Para que una referencia pueda leerse, el tipo subyacente debe tener la habilidad copy. Esta regla previene la copia de valores de recurso:

script {
fun example() {
// Dado que MyResource no tiene copy, esta función no compilará
let r = MyResource { f: 10 };
let r_ref: &MyResource = &r;
let r_copy: MyResource = *r_ref; // ERROR! No es posible copiar MyResource
}
}

Del mismo modo, para que una referencia pueda escribirse, el tipo subyacente debe tener la habilidad drop, ya que la escritura descarta (o “droppea”) el valor anterior.

script {
fun example() {
// Dado que MyResource no tiene drop, esta función no compilará
let r = MyResource { f: 10 };
let r_ref: &mut MyResource = &mut r;
*r_ref = MyResource { f: 20 }; // ERROR! No es posible droppear MyResource
}
}

Una referencia mutable puede usarse en un contexto donde se espera una referencia inmutable:

script {
fun example() {
let x = 7;
let y: &mut u64 = &mut x;
}
}
fun freeze_example() {
let x = 7;
let y: &mut u64 = &mut x;
let z: &u64 = y; // valid
}
fun freeze_example_2() {
let x = 7;
let y: &mut u64 = &mut x;
freeze_helper(y); // valid
}
fun freeze_helper(f: &u64) { }

En ambos casos, las anotaciones son opcionales. El compilador insertará operaciones freeze donde sea necesario.

fun freeze_example() {
let x = 7;
let y: &mut u64 = &mut x;
let z: &u64 = freeze(y); // `freeze` insertado por el compilador
}
fun freeze_example_2() {
let x = 7;
let y: &mut u64 = &mut x;
freeze_helper(freeze(y)); // `freeze` insertado por el compilador
}

Con esta operación freeze, el tipo &mut T puede considerarse un subtipo de &T. Esto significa que para cualquier expresión que use una referencia inmutable, una referencia mutable puede proporcionarse. Este comportamiento puede ser útil en muchos casos. Por ejemplo:

script {
fun example() {
let x = 7;
let y: &mut u64 = &mut x;
print_u64(y); // pasa una &mut u64 donde se espera &u64
}
}
fun print_u64(value: &u64) {
let tmp = *value;
// print tmp
}

Tanto las referencias mutables como las inmutables pueden extenderse siempre que no haya referencias mutables existentes. Esto es importante para prevenir violaciones de memoria y cargas de datos estáticos.

script {
fun example() {
let x = 7;
let y: &u64 = &x;
// let z: &mut u64 = &mut x; // error! x ya está prestado inmutablemente
}
}
script {
fun example() {
let x = 7;
let y: &mut u64 = &mut x;
// let z: &u64 = &x; // error! x ya está prestado mutablemente
// let z: &mut u64 = &mut x; // error! x ya está prestado mutablemente
}
}

Las referencias y tuplas que contienen referencias no pueden almacenarse en structs, lo que significa que no pueden existir en el almacenamiento global. Todas las referencias deben eliminarse antes del final de la función en la que se crearon. Esta restricción previene referencias colgantes, que podrían apuntar a datos inexistentes o reclamados.

struct Foo {
x: &u64, // error! structs no pueden contener referencias
}
struct Bar {
x: Foo<&u64>, // error! structs no pueden contener referencias
}

Comportamiento Temporal de Referencias Globales

Sección titulada «Comportamiento Temporal de Referencias Globales»

Cuando una función es llamada con expresiones BorrowGlobal, BorrowGlobalMut, o &, &mut de una operación de almacenamiento global, el compilador infiere automáticamente el tiempo de vida de la referencia para que sea el menor tiempo posible, incluyendo el punto donde la última vez se usa la referencia.

script {
use std::vector;
struct MyStruct {
x: u64
}
fun example() acquires MyStruct {
let obj = borrow_global_mut<MyStruct>(@0xa);
obj.x = 10;
// referencia obj automaticamente liberada aquí
// ok para llamar mutate_any_resource
mutate_any_resource();
}
fun mutate_any_resource() acquires MyStruct {
let obj = borrow_global_mut<MyStruct>(@0xa);
obj.x = 10;
}
}

En el ejemplo anterior, la función borrow_global_mut en example automáticamente “libera” obj después de obj.x = 10;, de modo que no hay referencias activas cuando mutate_any_resource es llamada. De lo contrario, habría un error en tiempo de compilación sobre referencias superpuestas.

Sin embargo, si la referencia se usa después de mutate_any_resource, entonces habría un error en tiempo de compilación:

script {
use std::vector;
struct MyStruct {
x: u64
}
fun example() acquires MyStruct {
let obj = borrow_global_mut<MyStruct>(@0xa);
obj.x = 10;
mutate_any_resource(); // error! obj está activa pero mutate_any_resource podría invalidarla
let tmp = obj.x; // obj se usa aquí
}
fun mutate_any_resource() acquires MyStruct {
let obj = borrow_global_mut<MyStruct>(@0xa);
obj.x = 10;
}
}
module 0x42::reference_examples {
struct Container {
value: u64,
name: vector<u8>,
}
struct NestedContainer {
inner: Container,
id: u64,
}
fun update_container_value(container: &mut Container, new_value: u64) {
container.value = new_value;
}
fun get_container_value(container: &Container): u64 {
container.value
}
fun update_nested_value(nested: &mut NestedContainer, new_value: u64) {
nested.inner.value = new_value;
}
fun get_nested_value(nested: &NestedContainer): u64 {
nested.inner.value
}
// Ejemplo de múltiples referencias
fun compare_containers(c1: &Container, c2: &Container): bool {
c1.value == c2.value
}
// Ejemplo de conversión de mutable a inmutable
fun read_after_write(container: &mut Container): u64 {
container.value = 42;
// Automáticamente convierte &mut Container a &Container
get_container_value(container)
}
}
module 0x42::vector_references {
use std::vector;
fun modify_vector_element(vec: &mut vector<u64>, index: u64, new_value: u64) {
let element_ref = vector::borrow_mut(vec, index);
*element_ref = new_value;
}
fun sum_vector_elements(vec: &vector<u64>): u64 {
let sum = 0;
let i = 0;
let len = vector::length(vec);
while (i < len) {
let element_ref = vector::borrow(vec, i);
sum = sum + *element_ref;
i = i + 1;
};
sum
}
fun swap_elements(vec: &mut vector<u64>, i: u64, j: u64) {
let temp = *vector::borrow(vec, i);
*vector::borrow_mut(vec, i) = *vector::borrow(vec, j);
*vector::borrow_mut(vec, j) = temp;
}
}
module 0x42::global_references {
struct GlobalCounter has key {
value: u64,
}
fun increment_counter(addr: address) acquires GlobalCounter {
let counter_ref = borrow_global_mut<GlobalCounter>(addr);
counter_ref.value = counter_ref.value + 1;
}
fun get_counter_value(addr: address): u64 acquires GlobalCounter {
let counter_ref = borrow_global<GlobalCounter>(addr);
counter_ref.value
}
fun reset_counter(addr: address) acquires GlobalCounter {
let counter_ref = borrow_global_mut<GlobalCounter>(addr);
counter_ref.value = 0;
}
// Ejemplo de múltiples referencias globales
fun compare_counters(addr1: address, addr2: address): bool acquires GlobalCounter {
let counter1_ref = borrow_global<GlobalCounter>(addr1);
let counter2_ref = borrow_global<GlobalCounter>(addr2);
counter1_ref.value == counter2_ref.value
}
}
// ❌ INCORRECTO - esto no compilará
module 0x42::bad_references {
fun dangling_reference(): &u64 {
let x = 10;
&x // ERROR: x sale del scope, la referencia sería colgante
}
}
// ✅ CORRECTO - retornar el valor en lugar de una referencia
module 0x42::good_references {
fun get_value(): u64 {
let x = 10;
x // OK: retorna el valor
}
}
// ❌ INCORRECTO - esto no compilará
module 0x42::bad_borrowing {
fun multiple_borrows_error() {
let x = 10;
let ref1 = &mut x;
let ref2 = &mut x; // ERROR: x ya está prestado mutablemente
*ref1 = 20;
*ref2 = 30;
}
}
// ✅ CORRECTO - usar las referencias secuencialmente
module 0x42::good_borrowing {
fun sequential_borrows() {
let x = 10;
{
let ref1 = &mut x;
*ref1 = 20;
}; // ref1 sale del scope aquí
{
let ref2 = &mut x;
*ref2 = 30;
}; // ref2 sale del scope aquí
}
}
// ❌ INCORRECTO - esto no compilará
module 0x42::bad_struct_refs {
struct BadStruct {
ref_field: &u64, // ERROR: structs no pueden contener referencias
}
}
// ✅ CORRECTO - almacenar valores en lugar de referencias
module 0x42::good_struct_refs {
struct GoodStruct {
value_field: u64, // OK: almacena el valor
}
fun work_with_references(s: &mut GoodStruct) {
let ref_to_field = &mut s.value_field; // OK: crear referencia temporalmente
*ref_to_field = 42;
}
}

1. Minimizar el Tiempo de Vida de las Referencias

Sección titulada «1. Minimizar el Tiempo de Vida de las Referencias»
// ✅ Buena práctica
fun process_data() acquires MyResource {
// Mantener las referencias por el tiempo mínimo necesario
{
let data_ref = borrow_global_mut<MyResource>(@0x1);
data_ref.value = compute_new_value();
} // Referencia liberada aquí
// Ahora es seguro hacer otras operaciones
other_function_that_might_borrow();
}

2. Usar freeze() Explícitamente Cuando Sea Apropiado

Sección titulada «2. Usar freeze() Explícitamente Cuando Sea Apropiado»
// ✅ Buena práctica - conversión explícita
fun explicit_freeze(data: &mut MyStruct): &MyStruct {
modify_data(data);
freeze(data) // Conversión explícita para claridad
}
// ❌ Puede ser confuso
fun deep_nesting(container: &mut OuterStruct) {
let inner_ref = &mut container.middle.inner.value;
*inner_ref = 42;
}
// ✅ Mejor práctica - funciones auxiliares
fun update_inner_value(container: &mut OuterStruct, new_value: u64) {
set_inner_value(&mut container.middle.inner, new_value);
}
fun set_inner_value(inner: &mut InnerStruct, value: u64) {
inner.value = value;
}

Las referencias en Move proporcionan una manera segura y eficiente de trabajar con datos sin transferir propiedad. Las reglas estrictas del sistema de tipos previenen errores comunes como referencias colgantes y races de datos, mientras permiten patrones de programación potentes y expresivos.

Puntos clave para recordar:

  • Las referencias inmutables (&) permiten solo lectura
  • Las referencias mutables (&mut) permiten lectura y escritura
  • Las referencias no pueden almacenarse en structs
  • El sistema de préstamos previene múltiples referencias mutables
  • El operador freeze convierte referencias mutables en inmutables
  • Las referencias tienen tiempos de vida automáticamente manejados