Menu
Coddy logo textTech

Access via Closures

Part of the Object Oriented Programming section of Coddy's Lua journey — lesson 45 of 70.

Private local variables are useless if you can never interact with them. The solution is to define getter and setter methods inside the constructor—where they have access to the closure's scope.

Since these methods are created inside :new(), they can "see" the local variables. You assign them directly to the object:

local Counter = {}
Counter.__index = Counter

function Counter:new()
    local count = 0  -- Private variable
    
    local obj = {}
    
    -- Define methods inside the constructor
    function obj:getCount()
        return count
    end
    
    function obj:increment()
        count = count + 1
    end
    
    setmetatable(obj, Counter)
    return obj
end

Now the private count variable can be accessed—but only through the methods you provide:

local c = Counter:new()
print(c:getCount())  -- 0
c:increment()
c:increment()
print(c:getCount())  -- 2
print(c.count)       -- nil (still hidden!)

Notice that :getCount() and :increment() are defined on obj directly, not on the class table. Each instance gets its own copy of these functions, each with access to its own private count. This is the trade-off: true privacy costs a bit more memory since methods aren't shared through the prototype.

challenge icon

Challenge

Easy

Let's build a Temperature class that uses closure-based privacy to store a temperature value in Celsius, with getter and setter methods defined inside the constructor to provide controlled access!

You'll organize your code across two files:

  • Temperature.lua: Create a class where the temperature value is stored in a local variable inside the constructor—completely hidden from outside access. The constructor :new(initialCelsius) should store the temperature in a local variable, then define these methods directly on the object instance:
    • :getCelsius() — returns the current temperature in Celsius
    • :setCelsius(value) — updates the temperature to a new value
    • :getFahrenheit() — returns the temperature converted to Fahrenheit using the formula: (celsius * 9/5) + 32
  • main.lua: Require your Temperature module and read two values from input: an initial temperature and a new temperature to set. Create a Temperature instance with the initial value, print the Celsius and Fahrenheit readings, then use the setter to change the temperature and print both readings again.

You will receive two inputs:

  1. The initial temperature in Celsius (a number)
  2. A new temperature to set (a number)

Your output should be four lines showing the temperature readings before and after the change:

Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}

For example, if the inputs are 0 and 100, the output should be:

Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212

Remember: the temperature value should be stored in a local variable inside :new(), not on self. The getter and setter methods are defined inside the constructor where they can access this private variable through closure. Attempting to access temp.celsius or temp._celsius directly should return nil!

Cheat sheet

To create truly private variables in Lua classes, define getter and setter methods inside the constructor where they have access to local variables through closure.

Methods are assigned directly to the object instance (not the class table), giving each instance its own copy with access to its own private variables:

local Counter = {}
Counter.__index = Counter

function Counter:new()
    local count = 0  -- Private variable
    
    local obj = {}
    
    -- Define methods inside the constructor
    function obj:getCount()
        return count
    end
    
    function obj:increment()
        count = count + 1
    end
    
    setmetatable(obj, Counter)
    return obj
end

Usage:

local c = Counter:new()
print(c:getCount())  -- 0
c:increment()
c:increment()
print(c:getCount())  -- 2
print(c.count)       -- nil (still hidden!)

Trade-off: Each instance gets its own copy of the methods (not shared through the prototype), which uses more memory but provides true privacy.

Try it yourself

-- Require the Temperature module
local Temperature = require('Temperature')

-- Read input values
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())

-- TODO: Create a Temperature instance with the initial value

-- TODO: Print the initial Celsius and Fahrenheit readings

-- TODO: Use the setter to change the temperature to newTemp

-- TODO: Print the updated Celsius and Fahrenheit readings
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming