tact icon indicating copy to clipboard operation
tact copied to clipboard

Feature Proposal: Late Initialization

Open mhbdev opened this issue 5 months ago • 0 comments

Tact Language Feature Proposal: Late Initialization

Summary

This proposal introduces a language feature for late initialization in Tact contracts, allowing developers to defer the initialization of certain contract fields until after deployment through dedicated initialization messages.

Motivation

Currently, Tact contracts require all non-optional fields to be initialized during contract deployment via the init() function. However, there are scenarios where:

  1. Gas Optimization: Reducing init data size to minimize deployment costs
  2. Dynamic Configuration: Setting up contract state based on runtime conditions
  3. Multi-step Initialization: Complex contracts that require initialization in multiple phases
  4. Conditional Setup: Different initialization paths based on deployment context

Current Workaround

Developers currently work around this limitation by:

  • Making fields optional (field: Type?)
  • Using boolean flags to track initialization state
  • Manually checking initialization in every function
contract Example {
    // Required minimal init data
    id: Int;
    deployer: Address;
    
    // Fields that should be initialized later
    owner: Address?;
    config: Config?;
    initialized: Bool = false;
    
    init(id: Int, deployer: Address) {
        self.id = id;
        self.deployer = deployer;
    }
    
    receive(msg: Initialize) {
        require(!self.initialized, "Already initialized");
        require(sender() == self.deployer, "Only deployer can initialize");
        
        self.owner = msg.owner;
        self.config = msg.config;
        self.initialized = true;
    }
    
    // Every function needs manual checks
    receive(msg: DoSomething) {
        require(self.initialized, "Contract not initialized");
        let owner = self.owner!!; // Force unwrap
        // ... rest of logic
    }
}

Proposed Solution

Introduce a late keyword for field declarations that:

  1. Allows fields to be uninitialized during deployment
  2. Provides compile-time guarantees about initialization
  3. Automatically generates initialization checks

Syntax

contract Example {
    // Regular init fields
    id: Int;
    deployer: Address;
    
    // Late initialization fields
    late owner: Address;
    late config: Config;
    
    init(id: Int, deployer: Address) {
        self.id = id;
        self.deployer = deployer;
    }
    
    receive(msg: Initialize) {
        require(sender() == self.deployer, "Only deployer can initialize");
        
        self.owner = msg.owner;
        self.config = msg.config;
    }
    
    receive(msg: DoSomething) {
        // Compiler automatically checks initialization
        // and provides access to late fields
        // throws LateInializationError exit code if field is not initialized
        let ownerAddr = self.owner; // No unwrapping needed
        // ... rest of logic
    }
}

Backward Compatibility

This feature is fully backward compatible:

  • Existing contracts continue to work unchanged
  • late is a new keyword that doesn't conflict with existing code
  • Optional migration path for existing contracts

Conclusion

Late initialization is a common pattern in smart contract development that currently requires verbose workarounds in Tact. This proposal provides a clean, type-safe solution that improves developer experience while maintaining the language's safety guarantees.

mhbdev avatar Jul 06 '25 23:07 mhbdev