Data Types

Move is a statically typed language, which means every variable and expression has a type that is known at compile time. Understanding the available data types is fundamental to writing effective Move code.

Primitive Types

Integer Types

Move provides several integer types with different sizes and characteristics.

Unsigned Integers

module my_module::integers {
    // 8-bit unsigned integer (0 to 255)
    public fun u8_example(): u8 {
        255u8
    }

    // 16-bit unsigned integer (0 to 65,535)
    public fun u16_example(): u16 {
        65535u16
    }

    // 32-bit unsigned integer (0 to 4,294,967,295)
    public fun u32_example(): u32 {
        4294967295u32
    }

    // 64-bit unsigned integer (0 to 18,446,744,073,709,551,615)
    public fun u64_example(): u64 {
        18446744073709551615u64
    }

    // 128-bit unsigned integer (0 to 2^128 - 1)
    public fun u128_example(): u128 {
        340282366920938463463374607431768211455u128
    }

    // 256-bit unsigned integer (0 to 2^256 - 1)
    public fun u256_example(): u256 {
        115792089237316195423570985008687907853269984665640564039457584007913129639935u256
    }
}

Integer Literals

public fun integer_literals() {
    // Default integer literals are u64
    let default_int: u64 = 42;
    
    // Explicit type annotations
    let small_int: u8 = 255u8;
    let medium_int: u16 = 65535u16;
    let large_int: u32 = 4294967295u32;
    let very_large_int: u128 = 340282366920938463463374607431768211455u128;
    
    // Hex literals
    let hex_u8: u8 = 0xFFu8;
    let hex_u64: u64 = 0xFFFFFFFFu64;
}

Integer Operations

public fun integer_operations() {
    let a: u64 = 10;
    let b: u64 = 3;
    
    // Arithmetic operations
    let sum = a + b;        // 13
    let difference = a - b;  // 7
    let product = a * b;     // 30
    let quotient = a / b;    // 3
    let remainder = a % b;   // 1
    
    // Bitwise operations
    let bitwise_and = a & b;  // 2
    let bitwise_or = a | b;   // 11
    let bitwise_xor = a ^ b;  // 9
    let left_shift = a << 2;  // 40
    let right_shift = a >> 1; // 5
    
    // Comparison operations
    let is_equal = a == b;     // false
    let is_not_equal = a != b; // true
    let is_greater = a > b;    // true
    let is_less = a < b;       // false
    let is_greater_equal = a >= b; // true
    let is_less_equal = a <= b;    // false
}

Boolean Type

The boolean type represents true or false values.

public fun boolean_examples() {
    // Boolean literals
    let true_value: bool = true;
    let false_value: bool = false;
    
    // Boolean operations
    let and_result = true && false;  // false
    let or_result = true || false;   // true
    let not_result = !true;          // false
    
    // Comparison results
    let comparison_result: bool = 5 > 3;  // true
    let equality_result: bool = 5 == 5;   // true
}

Address Type

Addresses represent 32-byte identifiers used for accounts, modules, and resources.

public fun address_examples() {
    // Address literals
    let account_address: address = @0x1;
    let custom_address: address = @0xABCDEF;
    let full_address: address = @0x0000000000000000000000000000000000000000000000000000000000000001;
    
    // Special addresses
    let zero_address: address = @0x0;
    let framework_address: address = @0x1;  // Aptos framework
    let token_address: address = @0x3;      // Legacy token standard
    let object_address: address = @0x4;     // Object standard
}

Vector Type

Vectors are dynamic arrays that can hold multiple values of the same type.

public fun vector_examples() {
    // Creating vectors
    let empty_vector: vector<u64> = vector::empty<u64>();
    let literal_vector: vector<u64> = vector[1, 2, 3, 4, 5];
    let string_vector: vector<String> = vector[
        string::utf8(b"hello"),
        string::utf8(b"world")
    ];
    
    // Vector operations
    let mut vec = vector[1, 2, 3];
    vector::push_back(&mut vec, 4);
    let length = vector::length(&vec);
    let first_element = *vector::borrow(&vec, 0);
    let last_element = vector::pop_back(&mut vec);
}

String Type

Strings are UTF-8 encoded text stored as vectors of bytes.

public fun string_examples() {
    // Creating strings
    let empty_string: String = string::utf8(b"");
    let hello_string: String = string::utf8(b"Hello, World!");
    let unicode_string: String = string::utf8(b"Hello 世界");
    
    // String operations
    let length = string::length(&hello_string);
    let is_empty = string::is_empty(&empty_string);
    let concatenated = string::utf8(b"Hello") + string::utf8(b" World");
    
    // String comparison
    let are_equal = hello_string == string::utf8(b"Hello, World!");
    let are_not_equal = hello_string != string::utf8(b"Goodbye");
}

Signer Type

The signer type represents the account that signed the transaction.

public fun signer_examples(account: &signer) {
    // Getting the address of the signer
    let account_address = signer::address_of(account);
    
    // Signer is used for resource operations
    // move_to(account, MyResource { data: 42 });
}

Type Abilities

Move types have abilities that determine how they can be used. The main abilities are:

  • copy: Can be copied
  • drop: Can be dropped (destroyed)
  • store: Can be stored in global storage
  • key: Can be used as a key in global storage

Ability Examples

module my_module::abilities {
    // Integer types have copy, drop, and store abilities
    public fun integer_abilities() {
        let x: u64 = 42;
        let y = x;  // Copy
        // x is still available due to copy ability
    }
    
    // Vector has copy, drop, and store abilities
    public fun vector_abilities() {
        let v1: vector<u64> = vector[1, 2, 3];
        let v2 = v1;  // Copy
        // v1 is still available
    }
    
    // String has copy, drop, and store abilities
    public fun string_abilities() {
        let s1: String = string::utf8(b"hello");
        let s2 = s1;  // Copy
        // s1 is still available
    }
    
    // Signer has drop ability only
    public fun signer_abilities(account: &signer) {
        let addr = signer::address_of(account);
        // Cannot copy signer, but can drop it
    }
}

Type Annotations

Move uses type annotations to specify the type of variables and function parameters.

public fun type_annotations() {
    // Explicit type annotations
    let explicit_u64: u64 = 42;
    let explicit_bool: bool = true;
    let explicit_address: address = @0x1;
    let explicit_vector: vector<u64> = vector[1, 2, 3];
    let explicit_string: String = string::utf8(b"hello");
    
    // Type inference (when possible)
    let inferred_u64 = 42;  // Inferred as u64
    let inferred_bool = true;  // Inferred as bool
    let inferred_vector = vector[1, 2, 3];  // Inferred as vector<u64>
}

Type Conversion

Move is strict about type conversions and requires explicit casting.

public fun type_conversion() {
    // Explicit casting between integer types
    let small: u8 = 255;
    let medium: u16 = (small as u16);
    let large: u64 = (medium as u64);
    
    // Casting to smaller types (may truncate)
    let large_num: u64 = 1000;
    let small_num: u8 = (large_num as u8);  // Will be 232 (1000 % 256)
    
    // Boolean to integer conversion
    let bool_val: bool = true;
    let int_val: u64 = (bool_val as u64);  // 1 for true, 0 for false
}

Generic Types

Move supports generic types for creating reusable code.

public fun generic_examples() {
    // Generic vector operations
    let int_vector: vector<u64> = vector[1, 2, 3];
    let string_vector: vector<String> = vector[
        string::utf8(b"hello"),
        string::utf8(b"world")
    ];
    
    // Generic function example
    let int_length = vector::length(&int_vector);
    let string_length = vector::length(&string_vector);
}

Type Safety

Move's type system prevents many common programming errors.

public fun type_safety_examples() {
    // This would cause a compilation error:
    // let x: u64 = "hello";  // Cannot assign string to u64
    
    // This would cause a compilation error:
    // let y: u8 = 256;  // Value too large for u8
    
    // This would cause a compilation error:
    // let z = x + "world";  // Cannot add u64 and string
    
    // Correct usage
    let x: u64 = 42;
    let y: u8 = 255;
    let z: u64 = x + (y as u64);
}

Best Practices

Choose Appropriate Types

public fun choose_types() {
    // Use u8 for small numbers (0-255)
    let age: u8 = 25;
    
    // Use u64 for most integer operations
    let balance: u64 = 1000000;
    
    // Use u128 for large financial calculations
    let total_supply: u128 = 1000000000000000000u128;
    
    // Use u256 for cryptographic operations
    let private_key: u256 = 0x1234567890ABCDEFu256;
}

Use Type Annotations for Clarity

public fun clear_types() {
    // Explicit types make code more readable
    let user_id: u64 = 12345;
    let is_active: bool = true;
    let user_address: address = @0xABCDEF;
    let user_name: String = string::utf8(b"Alice");
    
    // Avoid ambiguous literals
    let small_number: u8 = 42u8;  // Clear that this is u8
    let large_number: u128 = 1000000000000000000u128;  // Clear that this is u128
}

Handle Type Conversions Carefully

public fun safe_conversions() {
    let large: u64 = 1000;
    
    // Check bounds before converting to smaller types
    if (large <= 255) {
        let small: u8 = (large as u8);
        // Safe conversion
    } else {
        // Handle overflow case
        // abort 0
    }
    
    // Use appropriate types for calculations
    let a: u64 = 1000000;
    let b: u64 = 2000000;
    let result: u64 = a * b;  // May overflow u64
    
    // Consider using u128 for large calculations
    let safe_result: u128 = (a as u128) * (b as u128);
}

Use Vectors Efficiently

public fun vector_best_practices() {
    // Pre-allocate when you know the size
    let mut numbers = vector::empty<u64>();
    vector::push_back(&mut numbers, 1);
    vector::push_back(&mut numbers, 2);
    vector::push_back(&mut numbers, 3);
    
    // Use appropriate element types
    let small_numbers: vector<u8> = vector[1, 2, 3, 4, 5];
    let large_numbers: vector<u64> = vector[1000000, 2000000, 3000000];
    
    // Consider storage costs for large vectors
    // Each vector element in global storage uses a storage slot
}

Common Type Patterns

Error Handling with Types

enum Result<T> {
    Ok(T),
    Err(String),
}

public fun safe_divide(a: u64, b: u64): Result<u64> {
    if (b == 0) {
        return Result::Err(string::utf8(b"Division by zero"))
    };
    Result::Ok(a / b)
}

Option Type Pattern

enum Option<T> {
    Some(T),
    None,
}

public fun find_element(numbers: vector<u64>, target: u64): Option<u64> {
    let i = 0;
    let len = vector::length(&numbers);
    
    while (i < len) {
        let current = *vector::borrow(&numbers, i);
        if (current == target) {
            return Option::Some(current)
        };
        i = i + 1;
    };
    
    Option::None
}

Understanding these data types and their characteristics is essential for writing efficient and correct Move code. The type system helps prevent errors and makes code more maintainable and readable.