Structs and Abilities
Abilities are the mechanism Move uses to control what operations can be performed on a type. Every struct must explicitly declare which abilities it has. This chapter explores each ability in detail.
The Four Abilities
| Ability | What it allows |
|---|---|
copy | The value can be duplicated |
drop | The value can be implicitly destroyed when it goes out of scope |
store | The value can be stored inside another struct in global storage |
key | The value can be stored directly in global storage as a top-level resource |
copy -- Duplicating Values
The copy ability allows a value to be duplicated. Without it, assigning a value to a new variable moves it, making the original invalid.
struct Point has copy, drop {
x: u64,
y: u64,
}
fun copy_example() {
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // p1 is copied, both p1 and p2 are valid
let sum = p1.x + p2.x; // Works because Point has copy
}
A struct can only have
copyif all of its fields also havecopy.
drop -- Implicit Destruction
The drop ability allows a value to be automatically destroyed when it goes out of scope. Without drop, every value must be explicitly consumed.
struct TempData has drop {
value: u64,
}
fun drop_example() {
let data = TempData { value: 42 };
// data is implicitly dropped at end of scope -- no error
}
Without drop, the compiler would require you to destructure or move the value:
struct ImportantData {
value: u64,
}
fun no_drop_example() {
let data = ImportantData { value: 42 };
let ImportantData { value: _ } = data; // Must explicitly destroy
}
store -- Nested Storage
The store ability allows a value to be stored as a field inside another struct that is in global storage. Without store, a type cannot appear inside a resource.
struct Metadata has store, copy, drop {
name: vector<u8>,
version: u64,
}
struct Contract has key {
metadata: Metadata, // Metadata needs `store` to be here
owner: address,
}
key -- Top-Level Storage
The key ability allows a struct to be stored directly in global storage under an address. This is what makes a struct a "resource" in the traditional Move sense.
struct Balance has key {
amount: u64,
}
fun store_balance(account: &signer, amount: u64) {
move_to(account, Balance { amount });
}
A struct with
keyimplicitly requires all its fields to havestore.
Common Ability Combinations
Pure Data (Value Type)
/// Freely copyable and droppable -- behaves like a primitive
struct Config has copy, drop, store {
max_supply: u64,
is_active: bool,
}
Storable Asset (Resource)
/// Stored in global storage, cannot be copied or implicitly dropped
struct Token has key, store {
id: u64,
value: u64,
}
Temporary Object
/// Can be dropped but not copied or stored
struct Receipt has drop {
amount: u64,
timestamp: u64,
}
Hot Potato (No Abilities)
A struct with no abilities at all is called a "hot potato." It must be consumed by some specific function, which is useful for enforcing certain patterns.
/// Must be consumed -- cannot be copied, dropped, or stored
struct FlashLoanReceipt {
amount: u64,
fee: u64,
}
Ability Constraints on Fields
A struct's abilities are constrained by its fields:
- A struct can have
copyonly if all fields havecopy. - A struct can have
droponly if all fields havedrop. - A struct can have
storeonly if all fields havestore. - A struct can have
keyonly if all fields havestore.
// This would NOT compile because String does not have copy:
// struct BadExample has copy, drop {
// name: String, // String does not have copy
// }
Best Practices
- Start with minimal abilities: Only add what you need.
- Use
keyfor top-level resources: Structs stored under an address needkey. - Omit
copyfor assets: Digital assets should not be copyable. - Omit
dropfor assets that must be tracked: Prevent accidental loss by omittingdrop. - Use hot potato pattern for receipts: When you need to ensure a value is consumed by a specific function.