Saltearse al contenido

Structs y Resources

Un struct es una estructura de datos definida por el usuario que contiene campos tipados. Los structs pueden almacenar cualquier tipo no-referencia, incluyendo otros structs.

A menudo nos referimos a valores struct como resources si no pueden ser copiados y no pueden ser descartados. En este caso, los valores resource deben tener la propiedad transferida al final de la función. Esta propiedad hace que los resources sean particularmente apropiados para definir esquemas de almacenamiento global o para representar valores importantes (como un token).

Por defecto, los structs son lineales y efímeros. Con esto queremos decir que ellos: no pueden ser copiados, no pueden ser descartados, y no pueden ser almacenados en almacenamiento global. Esto significa que todos los valores deben tener propiedad transferida (lineal) y los valores deben ser tratados al final de la ejecución del programa (efímero). Podemos relajar este comportamiento dando al struct habilidades que permiten que los valores sean copiados o descartados y también almacenados en almacenamiento global o para definir esquemas de almacenamiento global.

Los structs deben ser definidos dentro de un módulo:

module 0x2::m {
struct Foo { x: u64, y: bool }
struct Bar {}
struct Baz { foo: Foo, }
// ^ nota: está bien tener una coma final
}

Los structs no pueden ser recursivos, por lo que la siguiente definición es inválida:

module 0x2::m {
struct Foo { x: Foo }
// ^ error! Foo no puede contener Foo
}

Como se mencionó arriba: por defecto, una declaración struct es lineal y efímera. Así que para permitir que el valor sea usado con ciertas operaciones (que lo copien, lo descarten, lo almacenen en almacenamiento global, o lo usen como esquema de almacenamiento), los structs pueden recibir habilidades anotándolos con has <ability>:

module 0x2::m {
struct Foo has copy, drop { x: u64, y: bool }
}

Para más detalles, ve la sección anotando structs.

Los structs deben comenzar con una letra mayúscula A a Z. Después de la primera letra, los nombres de struct pueden contener guiones bajos _, letras a a z, letras A a Z, o dígitos 0 a 9.

module 0x2::m {
struct Foo {}
struct BAR {}
struct B_a_z_4_2 {}
}

Esta restricción de nomenclatura de comenzar con A a Z está en lugar para dar espacio para futuras características del lenguaje. Puede o no ser removida más tarde.

Los valores de un tipo struct pueden ser creados (o “empaquetados”) indicando el nombre del struct, seguido por el valor para cada campo:

module 0x2::m {
struct Foo has drop { x: u64, y: bool }
struct Baz has drop { foo: Foo }
fun example() {
let foo = Foo { x: 0, y: false };
let baz = Baz { foo };
}
}

Si inicializas un campo struct con una variable local cuyo nombre es el mismo que el campo, puedes usar la siguiente abreviatura:

module 0x2::m {
fun example() {
let baz = Baz { foo: foo };
// es equivalente a
let baz = Baz { foo };
}
}

Esto a veces se llama “punning de nombre de campo”.

Los valores struct pueden ser destruidos enlazándolos o asignándolos a patrones.

module 0x2::m {
struct Foo { x: u64, y: bool }
struct Bar { foo: Foo }
struct Baz {}
fun example_destroy_foo() {
let foo = Foo { x: 3, y: false };
let Foo { x, y: foo_y } = foo;
// ^ abreviatura para `x: x`
// dos nuevos enlaces
// x: u64 = 3
// foo_y: bool = false
}
fun example_destroy_foo_wildcard() {
let foo = Foo { x: 3, y: false };
let Foo { x, y: _ } = foo;
// solo un nuevo enlace ya que y fue enlazado a un wildcard
// x: u64 = 3
}
fun example_destroy_foo_assignment() {
let x: u64;
let y: bool;
Foo { x, y } = Foo { x: 3, y: false };
// mutando variables existentes x & y
// x = 3, y = false
}
fun example_foo_ref() {
let foo = Foo { x: 3, y: false };
let Foo { x, y } = &foo;
// dos nuevos enlaces
// x: &u64
// y: &bool
}
}

Puedes usar el operador . para tomar prestado un campo de un struct:

module 0x2::m {
struct Foo { x: u64, y: bool }
fun example() {
let foo = Foo { x: 3, y: true };
let x_ref: &u64 = &foo.x;
let y_ref: &bool = &foo.y;
let x_ref_mut: &mut u64 = &mut foo.x;
let y_ref_mut: &mut bool = &mut foo.y;
}
}

El punto . puede ser usado para both valores owned y referencias:

fun example() {
let foo = Foo { x: 3, y: true };
let foo_ref = &foo;
let x = foo.x; // copia desde el valor owned
let x_ref = &foo.x; // borrow desde el valor owned
let x_ref_2 = &foo_ref.x; // borrow desde la referencia
}

Desde la Versión del Lenguaje 2.0

Los structs pueden ser definidos en una forma posicional, sin campos nombrados:

module 0x2::m {
struct Foo(u64, bool)
struct Bar()
struct Baz(Foo)
}

Los structs posicionales deben tener al menos un campo. Un struct posicional con solo un campo es común para crear un wrapper de tipo:

module 0x2::m {
struct Dollars(u64)
struct Euros(u64)
fun add_dollars(d1: Dollars, d2: Dollars): Dollars {
let Dollars(amount1) = d1;
let Dollars(amount2) = d2;
Dollars(amount1 + amount2)
}
}

Los structs pueden representar recursos cuando tienen la habilidad key y se almacenan en almacenamiento global:

module 0x42::example {
struct Counter has key {
value: u64
}
public fun create_counter(account: &signer) {
let counter = Counter { value: 0 };
move_to(account, counter);
}
public fun increment(addr: address): u64 acquires Counter {
let counter_ref = borrow_global_mut<Counter>(addr);
counter_ref.value = counter_ref.value + 1;
counter_ref.value
}
}

Los resources soportan operaciones especiales:

  • move_to<T>(signer, resource): Almacena un resource bajo la cuenta del signer
  • borrow_global<T>(address): Toma prestado un resource inmutablemente
  • borrow_global_mut<T>(address): Toma prestado un resource mutablemente
  • move_from<T>(address): Mueve un resource desde la cuenta
  • exists<T>(address): Verifica si un resource existe
module 0x42::simple_token {
struct Token has key {
value: u64
}
public fun mint(account: &signer, value: u64) {
let token = Token { value };
move_to(account, token);
}
public fun balance(addr: address): u64 acquires Token {
if (exists<Token>(addr)) {
borrow_global<Token>(addr).value
} else {
0
}
}
public fun transfer(from: &signer, to: address, amount: u64) acquires Token {
let from_addr = std::signer::address_of(from);
let from_token = borrow_global_mut<Token>(from_addr);
assert!(from_token.value >= amount, 1);
from_token.value = from_token.value - amount;
if (exists<Token>(to)) {
let to_token = borrow_global_mut<Token>(to);
to_token.value = to_token.value + amount;
} else {
let new_token = Token { value: amount };
move_to(account, new_token);
}
}
}
module 0x42::config {
struct Config has key {
admin: address,
fee_rate: u64,
enabled: bool
}
public fun initialize(admin: &signer, fee_rate: u64) {
let config = Config {
admin: std::signer::address_of(admin),
fee_rate,
enabled: true
};
move_to(admin, config);
}
public fun update_fee_rate(admin: &signer, new_rate: u64) acquires Config {
let admin_addr = std::signer::address_of(admin);
let config = borrow_global_mut<Config>(admin_addr);
assert!(config.admin == admin_addr, 1);
config.fee_rate = new_rate;
}
}
module 0x42::events {
use std::event;
struct TransferEvent has drop, store {
from: address,
to: address,
amount: u64
}
struct EventStore has key {
transfer_events: event::EventHandle<TransferEvent>
}
public fun initialize_events(account: &signer) {
move_to(account, EventStore {
transfer_events: event::new_event_handle<TransferEvent>(account)
});
}
public fun emit_transfer_event(
_account: &signer,
from: address,
to: address,
amount: u64
) acquires EventStore {
let event_store = borrow_global_mut<EventStore>(@0x42);
event::emit_event(&mut event_store.transfer_events, TransferEvent {
from,
to,
amount
});
}
}
  1. Usa nombres descriptivos: Los struct names deben describir claramente los datos que contienen:
// ✓ Bueno
struct UserProfile has key {
name: vector<u8>,
age: u8,
email: vector<u8>
}
// ✗ Poco claro
struct Data has key {
a: vector<u8>,
b: u8,
c: vector<u8>
}
  1. Minimiza habilidades: Solo otorga las habilidades que necesitas:
// ✓ Bueno: solo las habilidades necesarias
struct Config has key { ... }
// ✗ Demasiadas habilidades sin justificación
struct Config has key, copy, drop, store { ... }
  1. Validación en constructores: Valida datos cuando creates structs:
public fun create_user(name: vector<u8>, age: u8): User {
assert!(!vector::is_empty(&name), E_EMPTY_NAME);
assert!(age >= 18, E_UNDERAGE);
User { name, age }
}
  1. Encapsulación: Mantén campos privados y proporciona funciones de acceso:
module token {
struct Token has key {
value: u64 // campo privado
}
public fun value(token: &Token): u64 {
token.value
}
public fun set_value(token: &mut Token, new_value: u64) {
assert!(new_value > 0, E_INVALID_VALUE);
token.value = new_value;
}
}