Feature Proposal: Late Initialization
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:
- Gas Optimization: Reducing init data size to minimize deployment costs
- Dynamic Configuration: Setting up contract state based on runtime conditions
- Multi-step Initialization: Complex contracts that require initialization in multiple phases
- 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:
- Allows fields to be uninitialized during deployment
- Provides compile-time guarantees about initialization
- 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
lateis 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.