Skip to main content
The @return annotation documents function return values with type information, enabling IDE understanding of what functions produce and type-safe code.

Syntax

---@return <type> [variable_name] [description]

-- With comment separator
---@return <type> [variable_name] # description

-- Multiple return values
---@return <type1> [name1] [description1]
---@return <type2> [name2] [description2]

IDE Benefits

function validateInput(input)
    if input and input ~= "" then
        return true, "Valid input"
    else
        return false, "Invalid input"
    end
end

local result, msg = validateInput("test")
-- ❌ IDE doesn't know types of result or msg
-- ❌ No autocomplete on return values
-- ❌ No type checking on usage

Basic Return Values

1
Single Return Value
2
---@return string Username
function getCurrentUserName()
    return "John"
end

local name = getCurrentUserName()
-- IDE knows 'name' is string
3
Return Value with Variable Name
4
---@return number result Calculation result
function calculate(x, y)
    return x + y
end

local sum = calculate(10, 20)
-- IDE shows: 'sum' is number (result: Calculation result)
5
Multiple Return Values
6
Lua functions commonly return multiple values:
7
---@return boolean success Whether operation succeeded
---@return string message Result message
function validateInput(input)
    if input and input ~= "" then
        return true, "Input is valid"
    else
        return false, "Input cannot be empty"
    end
end

-- Usage
local success, message = validateInput("test")
if success then
    print(message)  -- IDE knows message is string
end

Optional Return Values

Use union types with nil for optional returns:
---@return User | nil User object, returns nil if not found
---@return string | nil Error message, returns nil if successful
function findUserById(id)
    local user = database:findUser(id)
    if user then
        return user, nil
    else
        return nil, "User not found"
    end
end

-- Usage with nil checking
local user, error = findUserById(123)
if user then
    print("Found:", user.name)  -- IDE knows user is User here
else
    print("Error:", error)       -- IDE knows error is string here
end

Complex Return Types

Table Structures

Define inline table shapes:
---@return {success: boolean, data: table[], count: number} Query result
function queryUsers(filters)
    local users = database:query("users", filters)
    return {
        success = true,
        data = users,
        count = #users
    }
end

-- Usage with autocomplete
local result = queryUsers({status = "active"})
print(result.success)  -- ✅ IDE autocompletes 'success'
print(result.data)     -- ✅ IDE autocompletes 'data'
print(result.count)    -- ✅ IDE autocompletes 'count'
print(result.invalid)  -- ⚠️ Warning: field doesn't exist

Function Return Values

Return functions with type signatures:
---@return fun(x: number): number Returns a multiplier function
function createMultiplier(factor)
    return function(x)
        return x * factor
    end
end

-- Usage
local double = createMultiplier(2)
local result = double(5)  -- IDE knows result is number

Generic Return Values

Preserve type information through generics:
---@generic T
---@param value T
---@return T Copy of input value
function clone(value)
    return deepCopy(value)
end

-- Type flows through
local numbers = {1, 2, 3}
local cloned = clone(numbers)  -- IDE knows cloned is number[]

local user = {name = "John", age = 25}
local clonedUser = clone(user)  -- IDE knows clonedUser has same structure

Variadic Return Values

Document functions that return multiple values:
---@return string ... All usernames
function getAllUserNames()
    return "John", "Jane", "Bob"
end

-- Capture all returns
local name1, name2, name3 = getAllUserNames()
-- All are known to be strings

Conditional Return Values

Document returns that vary based on parameters:
---@param includeDetails boolean Whether to include detailed information
---@return string name Username
---@return number age Age
---@return string? email Email (only returned when includeDetails is true)
function getUserInfo(includeDetails)
    local name, age = "John", 25
    if includeDetails then
        return name, age, "[email protected]"
    else
        return name, age
    end
end

-- Usage
local name, age = getUserInfo(false)
local name2, age2, email = getUserInfo(true)
-- IDE knows email might be nil

Error Handling Patterns

Success/Error Pattern

---@return boolean success Whether operation succeeded
---@return any result Result data on success
---@return string? error Error message on failure
function safeOperation(data)
    local success, result = pcall(function()
        return processData(data)
    end)
    
    if success then
        return true, result, nil
    else
        return false, nil, result  -- result is error message
    end
end

-- Usage
local ok, data, err = safeOperation(inputData)
if ok then
    print("Success:", data)
else
    print("Error:", err)
end

Result/Error Pattern

---@return table | nil result Operation result
---@return string | nil error Error message if failed
function performAction()
    if someCondition then
        return {status = "complete"}, nil
    else
        return nil, "Action failed"
    end
end

Iterator Return Values

Document iterator functions:
---@return fun(): number?, string? Iterator function
function iterateUsers()
    local users = {"John", "Jane", "Bob"}
    local index = 0
    
    return function()
        index = index + 1
        if index <= #users then
            return index, users[index]
        end
        return nil, nil
    end
end

-- Usage in for loop
for id, userName in iterateUsers() do
    -- IDE knows id is number and userName is string
    print(id, userName)
end

Async Return Values

---@async
---@return Promise<string> Promise of async operation
function fetchUserDataAsync(userId)
    return Promise.new(function(resolve, reject)
        setTimeout(function()
            if userId > 0 then
                resolve("User data")
            else
                reject("Invalid user ID")
            end
        end, 1000)
    end)
end

Before/After Comparison

Without @return

function findUser(id)
    if id > 0 then
        return {name = "John", age = 25}
    else
        return nil, "Invalid ID"
    end
end

local user, err = findUser(1)
-- ❌ Unknown if user is table or nil
-- ❌ Unknown if err is string or nil
-- ❌ No autocomplete on user
if user then
    print(user.name)  -- No type checking
end
Problems:
  • Return types unknown
  • No autocomplete on results
  • Type errors caught at runtime
  • Unclear API contract

With @return

---@return {name: string, age: number} | nil
---@return string | nil
function findUser(id)
    if id > 0 then
        return {name = "John", age = 25}
    else
        return nil, "Invalid ID"
    end
end

local user, err = findUser(1)
-- ✅ IDE knows user is table or nil
-- ✅ IDE knows err is string or nil
-- ✅ Autocomplete on user fields
if user then
    print(user.name)  -- ✅ Type checked
end
Benefits:
  • Clear return types
  • Full autocomplete support
  • Type validation
  • Clear API contract

Complete Example

---@class User
---@field id number
---@field name string
---@field email string
---@field age number

---@class QueryResult
---@field success boolean
---@field data User[]
---@field count number
---@field page number
---@field totalPages number

---@param filters {status?: string, minAge?: number, maxAge?: number}
---@param page? number Page number (default: 1)
---@param pageSize? number Items per page (default: 10)
---@return QueryResult | nil result Query result object
---@return string | nil error Error message if query failed
function queryUsers(filters, page, pageSize)
    page = page or 1
    pageSize = pageSize or 10
    
    local success, users = pcall(function()
        return database:query("users", filters, page, pageSize)
    end)
    
    if not success then
        return nil, "Database query failed: " .. tostring(users)
    end
    
    if not users or #users == 0 then
        return nil, "No users found matching criteria"
    end
    
    local totalCount = database:count("users", filters)
    local totalPages = math.ceil(totalCount / pageSize)
    
    return {
        success = true,
        data = users,
        count = #users,
        page = page,
        totalPages = totalPages
    }, nil
end

-- Usage with full type information
local result, err = queryUsers(
    {status = "active", minAge = 18},
    1,
    20
)

if result then
    -- IDE provides autocomplete for all fields
    print("Found", result.count, "users on page", result.page)
    print("Total pages:", result.totalPages)
    
    for _, user in ipairs(result.data) do
        -- IDE knows user is User with all fields
        print(user.id, user.name, user.email, user.age)
    end
else
    -- IDE knows err is string here
    print("Query failed:", err)
end

Best Practices

Even functions that return nil should document their return type for clarity.
The optional variable name helps clarify what each return value represents.
Explicitly show when a return value might be nil: User | nil.
List return values in the order they’re returned from the function.
Define the shape of returned tables for better autocomplete support.
Standardize on a pattern (e.g., value+error or success+value+error) across your codebase.

See Also

  • @param - Document function parameters
  • @async - Mark asynchronous functions
  • @overload - Define multiple function signatures

Build docs developers (and LLMs) love