5. Variables and Mutability
In Quorlin, variables serve as the fundamental mechanism for storing and manipulating data within smart contracts. Designed to feel familiar to developers coming from Java, C#, or Solidity, Quorlin uses plain, expressive English keywords (number, truth, address, text) while imposing strong compile-time type safety and explicit mutability controls.
This chapter covers how variables are declared, scoped, and mutated across two distinct memory domains: persistent contract storage and transient function execution memory. It also details how function mutability modifiers (reads and writes) enforce safety rules at compile time.
5.1 Overview of Variable Classification
Variables in Quorlin are categorized by their storage duration and accessibility:
- State Variables (Storage): Declared at the root scope of a
contract. State variables persist permanently on the Kortana blockchain within the unified state trie. - Local Variables (Transient Memory / Registers): Declared inside constructors or function bodies. Local variables exist only for the duration of a function execution and are discarded when execution completes.
- Implicit Context Variables: Built-in read-only values supplied by the Kortana Virtual Machine (KVM) execution context, such as
caller.
The following table summarizes the primary Quorlin data types, their internal compiler representation, and their corresponding Ethereum-compatible ABI types:
| Quorlin Type | Internal Type (sema) | ABI / EVM Equivalent | Description |
|---|---|---|---|
number | Type::U256 | uint256 | Unsigned 256-bit integer |
truth | Type::Bool | bool | Boolean value (yes or no) |
address | Type::Address | address | 20-byte Kortana / Ethereum address |
text | Type::Text | string | UTF-8 dynamic text string (subject to size bounds) |
map<K, V> | Mappings | N/A (Key-value store) | Key-value mapping pointing from key type K to value type V |
record | Custom Struct | Dynamic tuple | User-defined aggregate record type |
5.2 State Variables
State variables represent the persistent state of your smart contract. Every state variable declared inside a contract block is allocated a specific storage slot in the contract's storage space.
State Variable Declaration
State variables are declared at the contract scope prior to constructor and function definitions:
contract Vault { // State variable declarations address owner; number totalDeposited; truth isLocked; text vaultName; constructor { owner = caller; totalDeposited = 0; isLocked = no; vaultName = "Secure Vault"; } }
Storage Operations and KVM Cost Model
When a state variable is read or written, the Quorlin compiler translates these operations into explicit KVM storage operations:
- Reads (
get_storage): Reading a state variable queries the underlyingWorldStatetrie usingStateHost::get_storage(address, key). - Writes (
set_storage): Mutating a state variable updates the unified state trie viaStateHost::set_storage(address, key, value).
Because storage modifications alter the global state trie, modifying state variables incurs significantly higher gas costs than modifying local variables. The KVM gas schedule calculates gas based on whether a write fills an empty storage slot (cold write) or overwrites an existing slot (warm write).
5.3 Local Variables
Local variables are declared inside function bodies or constructors. They are allocated temporary space during program execution within KVM execution frames and registers.
Local Declaration and Assignment
Unlike state variables, local variables can be declared and initialized simultaneously within function execution blocks:
writes truth executeComputation(number inputVal) { // Local variable declaration and immediate assignment number stepOne = inputVal + 10; number stepTwo = stepOne * 2; // Local variable reassignment stepOne = stepTwo - 5; return yes; }
Scope and Lifetime
Local variables remain in scope only within the block ({ ... }) in which they are declared. Attempting to access a local variable outside its enclosing block yields a semantic compilation error. Local variables do not persist across KVM execution calls.
5.4 Primitive Types and Literals
Quorlin introduces clear, English-based primitives for literal values and operational logic.
1. number
The number type represents an unsigned 256-bit integer (uint256). Standard arithmetic operators (+, -, *, /, %) and bitwise operators (&, |, ^, <<, >>) operate on number types.
number price = 500; number fee = 15; number total = price + fee;
2. truth
The truth type replaces standard boolean indicators with explicit standard English keywords: yes and no.
truth active = yes; truth paused = no; if active { // Logical execution }
3. address
The address type holds a 20-byte standard blockchain account address. Hexadecimal address literals are prefixed with 0x.
address recipient = 0x1234567890abcdef1234567890abcdef12345678;
4. text
The text type represents UTF-8 character strings. String literals are enclosed in double quotes.
text standardGreeting = "Hello, Kortana!";
5.5 Function Mutability: reads vs writes
Quorlin strictly categorizes contract functions based on whether they read or mutate state variables. Mutability guarantees are enforced at compile time by the Quorlin Semantic Analyzer (Analyzer).
┌───────────────────────────┐
│ Function Definition │
└─────────────┬─────────────┘
│
Is it marked 'reads' or 'writes'?
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ reads │ │ writes │
└──────┬───────┘ └──────┬───────┘
│ │
Can read state vars Can read state vars
CANNOT write state vars CAN write state vars
(View / Pure Call) (State Transition)
Read-Only Functions (reads)
Functions tagged with the reads keyword guarantee that contract state variables will not be modified during their execution. A reads function can compute values, read local variables, and query state variables, but any attempt to mutate a state variable results in a compile error.
reads number getBalance(address account) { // Permitted: Reading from state mapping 'balances' return balances[account]; }
If a developer attempts to modify a state variable inside a reads function:
// INVALID: Will fail compilation during semantic analysis reads truth illegalMutation() { totalDeposited = totalDeposited + 1; // Semantic Error: Cannot write state in a 'reads' function return yes; }
State-Mutating Functions (writes)
Functions tagged with the writes keyword explicitly denote state-changing operations. They are permitted to alter state variables, write to mappings, instantiate updates, and emit event topics.
writes truth deposit() { number currentBalance = balances[caller]; balances[caller] = currentBalance + 100; totalDeposited = totalDeposited + 100; return yes; }
5.6 Mappings and Struct Records
Mappings (map<KeyType, ValueType>)
Mappings store key-value pairs inside contract state. Mappings are declared as state variables and modified via subscript assignment within writes functions.
contract TokenStore { map<address, number> balances; map<address, map<address, truth>> permissions; writes truth grantPermission(address operator) { permissions[caller][operator] = yes; return yes; } reads truth checkPermission(address owner, address operator) { return permissions[owner][operator]; } }
Key characteristics of mappings:
- Mappings are state-bound data structures and cannot be declared as local variables within function scope.
- Reading a non-existent key returns the zero-initialized default value of the target type (e.g.,
0fornumber,nofortruth).
Records (record)
Records allow grouping related primitive types into custom composite types. Records can be instantiated locally or used within state storage.
record UserProfile { number id; truth isActive; address wallet; } contract AccountRegistry { map<address, UserProfile> profiles; writes truth registerUser(number userId) { UserProfile newUser = UserProfile(userId, yes, caller); profiles[caller] = newUser; return yes; } reads UserProfile getProfile(address user) { return profiles[user]; } }
When accessing record fields, dot notation is used (e.g., userProfile.isActive).
5.7 Built-In Context Variables
Quorlin provides immutable, implicit variables made available by the KVM execution environment during run time.
caller
The caller keyword returns the address of the account or external contract initiating the current execution call frame. caller is immutable and cannot be reassigned.
writes truth reclaimOwnership() { require caller == owner, "Unauthorized caller"; // Execution continues... return yes; }
5.8 Comprehensive Example: Mutability Patterns
The contract below demonstrates variable types, variable scopes, and function mutability controls in Quorlin:
contract PropertyRegistry { // --- State Variables (Persistent Storage) --- address publicRegistryOwner; number totalRegisteredProperties; truth isRegistryOpen; record Property { number propertyId; address currentOwner; number registeredValue; } map<number, Property> registry; event PropertyRegistered(number indexed id, address indexed owner, number value); event PropertyTransferred(number indexed id, address indexed oldOwner, address indexed newOwner); constructor { publicRegistryOwner = caller; totalRegisteredProperties = 0; isRegistryOpen = yes; } // --- State Read Function --- reads Property getPropertyDetails(number id) { return registry[id]; } reads truth checkOwnership(number id, address checkAddr) { Property prop = registry[id]; return prop.currentOwner == checkAddr; } // --- State Write Function --- writes truth registerProperty(number id, number value) { require isRegistryOpen == yes, "Registry closed"; require registry[id].propertyId == 0, "Property already exists"; // Creating local struct record Property newProp = Property(id, caller, value); // Mutating State Storage registry[id] = newProp; totalRegisteredProperties = totalRegisteredProperties + 1; emit PropertyRegistered(id, caller, value); return yes; } writes truth transferProperty(number id, address newOwner) { Property currentProp = registry[id]; require currentProp.currentOwner == caller, "Not property owner"; // Local variable allocation address previousOwner = currentProp.currentOwner; // Struct field mutation & state updates currentProp.currentOwner = newOwner; registry[id] = currentProp; emit PropertyTransferred(id, previousOwner, newOwner); return yes; } }