Skip to content

Bytecode Type System

Move bytecode represents every type as a signature token — a tagged, recursive data structure stored in the Signature table. This page documents how types are encoded at the binary level, how abilities constrain them, and how the handle indirection model connects types to their definitions.

A SignatureToken is the core type representation in Move bytecode. Each token starts with a single tag byte that identifies the kind of type, optionally followed by additional data (an inner token, an index, or a list of type arguments).

TokenTagAdditional DataVersion
Bool0x01v5+
U80x02v5+
U640x03v5+
U1280x04v5+
Address0x05v5+
Reference0x06inner SignatureTokenv5+
MutableReference0x07inner SignatureTokenv5+
Struct0x08StructHandleIndex (ULEB128)v5+
TypeParameter0x09u16 index (ULEB128)v5+
Vector0x0Ainner SignatureTokenv5+
StructInstantiation0x0BStructHandleIndex + count + type arg tokensv5+
Signer0x0Cv5+
U160x0Dv6+
U320x0Ev6+
U2560x0Fv6+
Function0x10param tokens + return tokens + ability maskv8+
I80x11v9+
I160x12v9+
I320x13v9+
I640x14v9+
I1280x15v9+
I2560x16v9+

Primitive tokens carry no additional data — the tag byte alone identifies the type.

Unsigned integers: Bool (0x01), U8 (0x02), U64 (0x03), U128 (0x04), U16 (0x0D), U32 (0x0E), U256 (0x0F). All unsigned integer types have the abilities copy, drop, and store.

Signed integers (v9+): I8 (0x11), I16 (0x12), I32 (0x13), I64 (0x14), I128 (0x15), I256 (0x16). These share the same abilities as their unsigned counterparts.

Special types: Address (0x05) has copy + drop + store. Signer (0x0C) has only drop (it cannot be copied or stored).

Vector (0x0A) is followed by a single inner SignatureToken representing the element type. For example, vector<u64> serializes as 0x0A 0x03 — the Vector tag followed by the U64 tag. Vector inherits copy, drop, and store from its element type.

Struct (0x08) is followed by a ULEB128-encoded StructHandleIndex that points into the StructHandle table. This is used for non-generic struct types (or generic structs whose type parameters are not instantiated at this usage site).

StructInstantiation (0x0B) represents a generic struct with concrete type arguments. It is followed by:

  1. A ULEB128-encoded StructHandleIndex
  2. A ULEB128-encoded count of type arguments
  3. That many SignatureToken values, one per type argument

For example, Table<address, u64> would serialize as 0x0B <handle_idx> 0x02 0x05 0x03.

Reference (0x06) and MutableReference (0x07) each wrap a single inner SignatureToken. References always have copy + drop abilities. They cannot appear inside structs — the verifier rejects any struct field with a reference type.

TypeParameter (0x09) is followed by a ULEB128-encoded u16 index that refers to a type parameter of the enclosing generic struct or function. For example, in a function fun foo<T, U>(x: T), the parameter T appears as 0x09 0x00 (type parameter index 0) and U would be 0x09 0x01 (type parameter index 1).

Function (0x10) represents a first-class function type (used with closures). It serializes as:

  1. A ULEB128-encoded count of parameter types
  2. That many SignatureToken values for the parameters
  3. A ULEB128-encoded count of return types
  4. That many SignatureToken values for the returns
  5. A u8 ability bitmask (see Abilities)

The VM enforces a maximum nesting depth of 256 for signature tokens. Deeply nested types such as vector<vector<vector<...>>> are rejected during deserialization if they exceed this limit.

Move uses four abilities to control what operations a type supports. Abilities are encoded as a u8 bitmask, where each ability occupies a single bit position.

AbilityBit ValueDescription
copy0x01Values can be duplicated (via CopyLoc, ReadRef)
drop0x02Values can be discarded (via Pop, WriteRef, StLoc, leaving scope)
store0x04Values can exist inside a struct in global storage
key0x08The type can serve as a top-level key for global storage operations

The bitmask is the bitwise OR of the individual ability values. For example, copy + drop + store encodes as 0x01 | 0x02 | 0x04 = 0x07. The empty set is 0x00.

Set NameAbilitiesBitmaskUsed By
EMPTY(none)0x00
PRIMITIVEScopy + drop + store0x07Bool, U8, U64, U128, Address, integers
SIGNERdrop0x02Signer
REFERENCEScopy + drop0x03Reference, MutableReference
FUNCTIONSdrop0x02Minimum for function types
ALLcopy + drop + store + key0x0F

The bytecode verifier checks abilities before allowing certain instructions:

  • CopyLoc and ReadRef require the type to have copy.
  • Pop, WriteRef, StLoc (when overwriting), and Eq/Neq require drop. A value left in a local when Ret executes also requires drop.
  • MoveTo requires the type to have key. MoveFrom, BorrowGlobal, BorrowGlobalMut, and Exists also require key.
  • Fields of a struct with key must have store (since they reside in global storage).

When a generic struct S<T> declares has copy, drop, the type parameter T must satisfy certain ability constraints for the instantiation S<ConcreteType> to have those abilities. The rule is:

  • For S<T> to have ability a, every non-phantom type parameter T must have a.requires().
  • The requires mapping is: copy requires copy, drop requires drop, store requires store, and key requires store.

These constraints are stored in the StructTypeParameter struct, which pairs each type parameter with an AbilitySet of constraints and a is_phantom flag.

A type parameter declared as phantom does not contribute to the ability requirements of the struct. A phantom parameter does not appear in any field of the struct — it exists only as a type tag. For example, in struct Coin<phantom T> has store { value: u64 }, the type T carries no ability constraints because it is phantom.

At the bytecode level, StructTypeParameter records is_phantom: true for phantom parameters. The verifier confirms that phantom parameters are never used in non-phantom positions within field types.

Move bytecode does not inline type names, addresses, or signatures directly into instructions. Instead, it uses a system of indices that point into shared tables. This indirection provides compact binary encoding, deduplication of repeated references, and efficient module loading.

Index TypePoints ToRust Type
ModuleHandleIndexModule handle tableu16
StructHandleIndexStruct handle tableu16
FunctionHandleIndexFunction handle tableu16
FieldHandleIndexField handle tableu16
SignatureIndexSignature tableu16
IdentifierIndexIdentifier (string) tableu16
AddressIdentifierIndexAddress tableu16
ConstantPoolIndexConstant poolu16
StructDefinitionIndexStruct definition tableu16
FunctionDefinitionIndexFunction definition tableu16
StructDefInstantiationIndexStruct instantiation tableu16
FunctionInstantiationIndexFunction instantiation tableu16
FieldInstantiationIndexField instantiation tableu16

All index types are u16 values (maximum 65,535 entries per table). They are serialized as ULEB128 in the binary format.

  1. Compact binary size. A function might reference the same struct type dozens of times. With indices, each reference is a 1—2 byte ULEB128 value instead of a full module address + name string.
  2. Deduplication. Identical signatures, identifiers, and addresses are stored once and referenced by index. The serializer ensures no duplicate entries exist in any table.
  3. Efficient loading. The VM can resolve handles once during module loading and cache the results. Instructions then operate on pre-resolved data.

Consider the instruction Call(FunctionHandleIndex(3)). The VM resolves the call target through a chain of table lookups:

Instruction: Call(FunctionHandleIndex(3))
|
v
FunctionHandle #3:
module: ModuleHandleIndex(0) ----> ModuleHandle #0:
name: IdentifierIndex(5) address: AddressIdentifierIndex(1) -> 0x1
parameters: SignatureIndex(2) name: IdentifierIndex(2) -> "vector"
return_: SignatureIndex(1)
|
v
IdentifierIndex(5) -> "push_back"
SignatureIndex(2) -> [Vector(TypeParameter(0)), TypeParameter(0)]
SignatureIndex(1) -> []

The VM chains through: instruction operand to function handle to module handle (and from there to the account address and module name), identifier table (for the function name), and signature table (for parameter and return types). Each step is an array lookup by index.

The same pattern applies to type resolution. A Struct(StructHandleIndex(2)) signature token resolves through:

SignatureToken: Struct(StructHandleIndex(2))
|
v
StructHandle #2:
module: ModuleHandleIndex(1) -> address + module name
name: IdentifierIndex(4) -> "Coin"
abilities: 0x07 -> copy + drop + store
type_parameters: [] -> (non-generic)

Move supports generic (parameterized) types and functions. At the bytecode level, generics use type parameter indices and instantiation tables to avoid duplicating definitions for each concrete type.

Type parameters are represented as u16 indices (TypeParameterIndex). Inside a generic function or struct definition, references to type parameters use the TypeParameter(index) signature token. Index 0 is the first type parameter, index 1 is the second, and so on.

  • In a struct handle, type parameters are stored as a Vec<StructTypeParameter>, where each entry carries ability constraints and a phantom flag.
  • In a function handle, type parameters are stored as a Vec<AbilitySet>, listing the required abilities for each type parameter.

When generic functions or structs are used with concrete type arguments, the bytecode stores the instantiation in a separate table rather than duplicating the handle.

FunctionInstantiation pairs a FunctionHandleIndex with a SignatureIndex that holds the concrete type arguments:

FieldTypeDescription
handleFunctionHandleIndexThe generic function being instantiated
type_parametersSignatureIndexIndex into Signature table holding type args

StructDefInstantiation pairs a StructDefinitionIndex with a SignatureIndex:

FieldTypeDescription
defStructDefinitionIndexThe generic struct definition
type_parametersSignatureIndexIndex into Signature table holding type args

FieldInstantiation pairs a FieldHandleIndex with a SignatureIndex:

FieldTypeDescription
handleFieldHandleIndexThe field in a generic struct
type_parametersSignatureIndexIndex into Signature table holding type args

Instructions that operate on generic types or functions come in paired forms: a base instruction and a *Generic variant. The base instruction uses a direct handle or definition index, while the generic variant uses an instantiation index.

Base InstructionGeneric VariantOperand Type
CallCallGenericFunctionInstantiationIndex
PackPackGenericStructDefInstantiationIndex
UnpackUnpackGenericStructDefInstantiationIndex
ExistsExistsGenericStructDefInstantiationIndex
MoveFromMoveFromGenericStructDefInstantiationIndex
MoveToMoveToGenericStructDefInstantiationIndex
ImmBorrowGlobalImmBorrowGlobalGenericStructDefInstantiationIndex
MutBorrowGlobalMutBorrowGlobalGenericStructDefInstantiationIndex
ImmBorrowFieldImmBorrowFieldGenericFieldInstantiationIndex
MutBorrowFieldMutBorrowFieldGenericFieldInstantiationIndex

Worked Example: vector::push_back<u64>(v, 42)

Section titled “Worked Example: vector::push_back<u64>(v, 42)”

This call to a generic function compiles to a CallGeneric instruction. Here is the resolution chain:

  1. The compiler emits CallGeneric(FunctionInstantiationIndex(N)).
  2. FunctionInstantiation #N contains:
    • handle: FunctionHandleIndex(M) (pointing to the push_back function handle)
    • type_parameters: SignatureIndex(K) (pointing to a signature containing [U64])
  3. FunctionHandle #M contains:
    • module: ModuleHandleIndex pointing to 0x1::vector
    • name: IdentifierIndex pointing to "push_back"
    • parameters: SignatureIndex pointing to [Vector(TypeParameter(0)), TypeParameter(0)]
    • return_: SignatureIndex pointing to []
    • type_parameters: [AbilitySet::EMPTY] (no constraints on T)
  4. At runtime, the VM substitutes TypeParameter(0) with U64 from the instantiation, yielding effective parameter types [vector<u64>, u64].

Bytecode version 8 introduced first-class function types to support closures. A function type describes the signature of a callable value — its parameter types, return types, and the abilities the closure must satisfy.

The Function signature token (tag 0x10) carries:

ComponentEncoding
Parameter countULEB128
Parameter typesSequence of SignatureToken values
Return countULEB128
Return typesSequence of SignatureToken values
Abilitiesu8 bitmask

For example, a function type |u64, bool| -> address with drop ability serializes as: 0x10 0x02 0x03 0x01 0x01 0x05 0x02 — Function tag, 2 params, U64, Bool, 1 return, Address, drop bitmask (0x02).

All function types have at least the drop ability. Public functions also get copy and store. Private functions get copy and drop but not store (since they may not be valid after a module upgrade).

Three instructions work with function types:

  • PackClosure(FunctionHandleIndex, ClosureMask) (opcode 0x58) — Creates a closure by capturing some arguments of the named function. The ClosureMask is a u64 bitmask indicating which parameters are captured from the stack (bit set = captured). The remaining parameters become the closure’s parameter types.

  • PackClosureGeneric(FunctionInstantiationIndex, ClosureMask) (opcode 0x59) — Same as PackClosure but for a generic function instantiation.

  • CallClosure(SignatureIndex) (opcode 0x5A) — Invokes a closure. The SignatureIndex describes the expected function type. The closure value sits on top of the stack, with the remaining (non-captured) arguments below it.

Given a function fun add(x: u64, y: u64): u64, creating and calling a closure that captures the first argument:

// Capture x=5, leaving a closure of type |u64| -> u64
LdU64(5)
PackClosure(FunctionHandleIndex(add), mask=0b01)
// Stack: [closure]
// Call the closure with y=10
LdU64(10)
CallClosure(SignatureIndex(|u64| -> u64))
// Stack: [15]

The mask 0b01 means “capture parameter 0.” The resulting closure takes one remaining argument (parameter 1) and returns u64.

A struct (or enum) type in Move bytecode is split across two layers: a handle that describes the type’s identity and a definition that provides its fields.

The StructHandle describes a struct type’s public interface:

FieldTypeDescription
moduleModuleHandleIndexThe module that defines this type
nameIdentifierIndexThe name of the type
abilitiesAbilitySet (u8)Declared abilities of the type
type_parametersVec<StructTypeParameter>Type parameter constraints and phantom flags

Each StructTypeParameter consists of:

FieldTypeDescription
constraintsAbilitySetAbilities required of the type argument
is_phantomboolWhether the parameter is phantom

The StructDefinition connects a handle to the type’s field layout:

FieldTypeDescription
struct_handleStructHandleIndexPoints to the corresponding StructHandle
field_informationStructFieldInformationField layout (native, declared, or variants)

StructFieldInformation is an enum with three variants:

VariantSerialization TagContent
Native0x01No fields (native type)
Declared0x02List of FieldDefinition entries
DeclaredVariants0x03List of VariantDefinition entries (v7+)

Each field in a struct is represented by:

FieldTypeDescription
nameIdentifierIndexName of the field
signatureTypeSignatureThe field’s type (a SignatureToken)

Bytecode version 7 added enum types using the DeclaredVariants field information. Each VariantDefinition contains:

FieldTypeDescription
nameIdentifierIndexName of the variant
fieldsVec<FieldDefinition>Fields specific to this variant

The variant index is a u16 value determined by the variant’s position in the list. Additional tables support variant operations:

  • StructVariantHandle — pairs a StructDefinitionIndex with a VariantIndex to identify a specific variant.
  • StructVariantInstantiation — pairs a StructVariantHandleIndex with a SignatureIndex for generic variant operations.
  • VariantFieldHandle — identifies a field shared across multiple variants of the same enum.
  • VariantFieldInstantiation — generic version of VariantFieldHandle.

Function visibility and access control are encoded directly in the bytecode, governing who can call a function and what resources it may touch.

The Visibility enum is stored as a u8 in each FunctionDefinition:

VisibilityValueDescription
Private0x00Callable only within the defining module
Public0x01Callable from any module or script
Friend0x03Callable from the defining module and declared friend modules

Value 0x02 was previously used for Script visibility but is now deprecated in favor of the separate entry modifier.

The is_entry flag (serialized as a u8 where 0x04 represents the entry bit) marks a function as a valid transaction entry point. Entry functions can be called directly by the Aptos transaction runtime. A function can be both public and entry, or private and entry.

Bytecode version 7 added access specifiers to function handles. An access specifier describes which global resources a function reads or writes, enabling static analysis of a function’s storage footprint.

Each AccessSpecifier contains:

FieldTypeDescription
kindAccessKindReads (0x01) or Writes (0x02)
negatedboolWhether the specifier is negated
resourceResourceSpecifierWhich resource(s) are accessed
addressAddressSpecifierAt which address(es)

ResourceSpecifier variants:

VariantTagDescription
Any0x01Any resource
DeclaredAtAddress0x02Resources declared at a specific address
DeclaredInModule0x03Resources declared in a specific module
Resource0x04A specific struct type
ResourceInstantiation0x05A specific generic struct instantiation

AddressSpecifier variants:

VariantTagDescription
Any0x01Any storage address
Literal0x02A specific literal address
Parameter0x03Derived from a function parameter (optionally via a known function like object::address_of)

If a function handle has no access specifiers (None), the VM assumes the function may access arbitrary resources. An empty specifier list (Some([])) indicates a pure function with no global storage dependencies.