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
Operadores de Referencia
Sección titulada «Operadores de Referencia»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
”.
Sintaxis | Tipo | Descripción |
---|---|---|
&e | &T donde e: T y T es un tipo no-referencia | Crear una referencia inmutable a e |
&mut e | &mut T donde e: T y T es un tipo no-referencia | Crear una referencia mutable a e . |
&e.f | &T donde e.f: T | Crear una referencia inmutable al campo f del struct e . |
&mut e.f | &mut T donde e.f: T | Crear una referencia mutable al campo f del struct e . |
freeze(e) | &T donde e: &mut T | Convertir 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.
Sintaxis | Tipo | Descripción |
---|---|---|
*e | T donde e es &T o &mut T | Leer el valor apuntado por e |
*e1 = e2 | () donde e1: &mut T y e2: T | Actualizar 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 }}
freeze
inference
Sección titulada «freeze inference»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}
Reglas de Subtyping con Referencias
Sección titulada «Reglas de Subtyping con Referencias»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}
Propiedad
Sección titulada «Propiedad»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 }}
Referencias No Pueden Almacenarse
Sección titulada «Referencias No Pueden Almacenarse»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; }}
Ejemplos Avanzados
Sección titulada «Ejemplos Avanzados»Trabajando con Referencias en Structs
Sección titulada «Trabajando con Referencias en Structs»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) }}
Patrones de Referencias con Vectores
Sección titulada «Patrones de Referencias con Vectores»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; }}
Referencias con Recursos Globales
Sección titulada «Referencias con Recursos Globales»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 }}
Errores Comunes y Cómo Evitarlos
Sección titulada «Errores Comunes y Cómo Evitarlos»Error de Referencia Colgante
Sección titulada «Error de Referencia Colgante»// ❌ 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 referenciamodule 0x42::good_references { fun get_value(): u64 { let x = 10; x // OK: retorna el valor }}
Error de Préstamos Múltiples
Sección titulada «Error de Préstamos Múltiples»// ❌ 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 secuencialmentemodule 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í }}
Error de Uso de Referencias en Structs
Sección titulada «Error de Uso de Referencias en Structs»// ❌ 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 referenciasmodule 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; }}
Mejores Prácticas
Sección titulada «Mejores Prácticas»1. Minimizar el Tiempo de Vida de las Referencias
Sección titulada «1. Minimizar el Tiempo de Vida de las Referencias»// ✅ Buena prácticafun 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ícitafun explicit_freeze(data: &mut MyStruct): &MyStruct { modify_data(data); freeze(data) // Conversión explícita para claridad}
3. Evitar Referencias Anidadas Profundas
Sección titulada «3. Evitar Referencias Anidadas Profundas»// ❌ Puede ser confusofun deep_nesting(container: &mut OuterStruct) { let inner_ref = &mut container.middle.inner.value; *inner_ref = 42;}
// ✅ Mejor práctica - funciones auxiliaresfun 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;}
Conclusión
Sección titulada «Conclusión»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