루아(Lua) 프로그래밍: 변수와 타입 시스템 이해하기

53 sec read

프로그램의 기본적인 문법과 데이터 종류를 익혔다면, 이제 그 데이터들을 담고 관리하는 가장 핵심적인 도구인 변수(Variable)에 대해 깊이 알아볼 차례입니다. 변수는 프로그래밍에서 데이터를 저장하고, 이름을 붙여 필요할 때마다 참조하거나 변경할 수 있게 해주는 가장 기본적인 구성 요소입니다.

이 글에서는 Lua의 변수 사용법, 특히 변수의 유효 범위(Scope)를 결정하는 중요한 개념과 Lua의 유연한 타입 시스템을 효과적으로 다루는 방법을 자세히 탐구합니다. 이 내용을 이해하면 더 견고하고 효율적인 코드를 작성하는 강력한 기반을 다질 수 있습니다.

변수: 데이터에 이름표를 붙이는 기술

변수를 가장 쉽게 비유하자면, ‘이름표가 붙은 데이터 보관함’입니다. 우리는 이름표(변수 이름)를 통해 보관함(메모리 공간)에 어떤 데이터(값)가 들어있는지 쉽게 확인하고, 필요하면 다른 데이터로 교체할 수 있습니다.

Lua에서 변수를 사용하면 다음과 같은 명확한 이점을 얻습니다.

  • 데이터 재사용: 한 번 저장된 값을 여러 곳에서 이름만으로 쉽게 가져와 사용할 수 있습니다.
  • 가독성 향상: userScore라는 이름은 그냥 숫자 100보다 코드의 의도를 훨씬 명확하게 전달합니다.
  • 유지보수 용이성: 값이 바뀌어야 할 때, 변수에 할당된 값만 한 번 수정하면 그 변수를 사용하는 모든 곳에 즉시 반영됩니다.

변수를 사용하는 방법은 간단합니다. 변수 이름을 정하고 할당 연산자(=)를 사용해 값을 저장(할당)하면 됩니다.

local score = 100
local playerName = "Alice"

-- 변수의 값은 언제든지 변경할 수 있습니다.
score = score + 50 -- 기존 score 값에 50을 더해 다시 할당
print(score)       -- 출력: 150

전역 변수와 지역 변수: 변수의 활동 무대

위 예제에서 사용된 local 키워드는 변수가 활동할 수 있는 유효 범위, 즉 스코프(Scope)를 지정합니다. Lua의 변수는 스코프에 따라 두 종류로 나뉩니다.

전역 변수 (Global Variables)

local 키워드 없이 선언된 변수는 기본적으로 전역 변수가 됩니다. 이름처럼 프로그램의 어디에서든 접근할 수 있는 ‘공용’ 변수입니다.

-- local 키워드 없이 선언하면 전역 변수가 됩니다.
globalMessage = "이것은 전역 메시지입니다."

function printGlobal()
  print(globalMessage) -- 함수 안에서도 자유롭게 접근 가능
end

printGlobal() -- 출력: 이것은 전역 메시지입니다.

-- 선언되지 않은 전역 변수에 접근하면 오류 대신 nil 값을 반환합니다.
print(anotherGlobal) -- 출력: nil

지역 변수 (Local Variables)

local 키워드를 사용하여 선언된 변수입니다. 지역 변수는 자신이 선언된 코드 블록(함수, 제어문, do...end 블록 등) 안에서만 유효합니다. 해당 블록을 벗어나면 더 이상 접근할 수 없습니다.

local localMessage = "이것은 지역 메시지입니다."

if true then
  local blockVariable = "블록 안에서만 유효한 변수"
  print(localMessage)  -- 상위 블록의 지역 변수는 접근 가능
  print(blockVariable) -- 출력: 블록 안에서만 유효한 변수
end

-- print(blockVariable) -- 오류 발생! if 블록 밖에서는 blockVariable에 접근할 수 없습니다.

왜 항상 local을 사용해야 할까요?

특별한 이유가 없는 한, 모든 변수는 local 키워드로 선언하는 것이 Lua 프로그래밍의 가장 중요한 원칙입니다. 그 이유는 명확합니다.

  • 성능: 지역 변수는 전역 변수보다 접근 속도가 훨씬 빠릅니다. Lua 인터프리터가 지역 변수를 더 효율적으로 처리하기 때문입니다.
  • 이름 충돌 방지: 서로 다른 코드 블록에서 같은 이름의 지역 변수를 사용해도 서로 전혀 영향을 주지 않습니다. 전역 변수를 남용하면, 나도 모르게 다른 곳에서 사용 중인 중요한 변수의 값을 덮어쓰는 심각한 버그를 유발할 수 있습니다.
  • 가독성 및 유지보수: 변수의 사용 범위가 명확해져 코드를 이해하고 추적하기 쉬워집니다.
  • 메모리 관리: 지역 변수는 자신이 속한 블록의 실행이 끝나면 더 이상 필요 없는 데이터로 간주되어, 메모리를 더 효율적으로 관리하는 데 도움이 됩니다.

실수로 local을 빠뜨려 의도치 않은 전역 변수를 만드는 것은 디버깅하기 매우 어려운 문제의 원인이 되므로 항상 주의해야 합니다.

변수를 다루는 실용적인 기술들

이름 규칙과 다중 할당

변수 이름은 그 안에 담긴 데이터의 의미를 잘 나타내도록 짓는 것이 좋습니다. 일반적으로 여러 단어를 조합할 때 playerName처럼 중간에 대문자를 섞는 카멜 케이스(camelCase)player_name처럼 밑줄을 사용하는 스네이크 케이스(snake_case)가 널리 쓰입니다.

또한 Lua는 한 번에 여러 변수에 값을 할당하는 다중 할당(Multiple Assignment)을 지원하여 코드를 간결하게 만들어 줍니다.

local x, y, z = 10, 20, 30

-- 값의 개수가 변수보다 많으면 남는 값은 무시됩니다.
local a, b = 100, 200, 300 -- a는 100, b는 200이 됨

-- 값이 부족하면 해당 변수는 nil로 채워집니다.
local p, q, r = 5, 8       -- p는 5, q는 8, r은 nil이 됨

-- 변수의 값을 서로 맞바꿀 때 매우 유용합니다.
local tempA = 1
local tempB = 2
tempA, tempB = tempB, tempA -- tempA와 tempB의 값을 한 줄로 교환
print(tempA, tempB)         -- 출력: 2    1

유연함의 미학: 동적 타이핑과 타입 다루기

Lua는 동적 타입 언어입니다. 이는 변수의 타입이 고정되어 있지 않고, 실행 중에 할당되는 값에 따라 언제든지 변경될 수 있음을 의미합니다.

local myVar = 10       -- 현재 myVar는 number 타입
print(type(myVar))     -- 출력: number

myVar = "이제 문자열" -- string 타입으로 변경됨
print(type(myVar))     -- 출력: string

이러한 유연성은 개발 속도를 높여주지만, 예상치 못한 타입의 값이 변수에 할당되어 오류를 일으킬 수도 있습니다. 따라서 필요할 때 변수의 타입을 확인하고 제어하는 것이 중요합니다.

  • type() 함수: 변수나 값의 타입을 문자열로 반환하여 현재 타입을 확인할 수 있습니다.
if type(data) == "number" then
  print("숫자 처리:", data + 1)
elseif type(data) == "string" then
  print("문자열 처리:", string.upper(data))
end
  • assert() 함수: 특정 조건을 검사하고, 만약 조건이 거짓(false 또는 nil)이면 오류를 발생시켜 프로그램을 중단시킵니다. 함수의 인자나 중요한 변수의 타입을 강제할 때 유용합니다.
local function process_user(user_table)
  -- user_table이 테이블이 아니면 오류를 발생시키고 메시지를 출력
  assert(type(user_table) == "table", "user_table은 테이블이어야 합니다.")

  print("사용자 처리:", user_table.name)
end

타입 변환: 자동 처리와 수동 제어

Lua는 특정 연산에서 필요에 따라 자동으로 타입을 변환(Type Coercion)하기도 합니다.

  • 문자열 → 숫자: 산술 연산(+, -, * 등)을 할 때, 숫자 형태의 문자열은 자동으로 숫자로 변환됩니다.
    "10" + 515가 됩니다.
  • 숫자 → 문자열: 문자열 연결 연산자(..)를 사용하면 숫자가 자동으로 문자열로 변환됩니다.
    "점수: " .. 100"점수: 100"이 됩니다.

이러한 자동 변환은 편리하지만, 때로는 명시적으로 타입을 변환해주는 것이 더 안전하고 코드의 의도를 명확하게 합니다.

  • tostring(value): 어떤 값이든 문자열로 변환합니다.
  • tonumber(value): 문자열을 숫자로 변환합니다. 변환이 불가능하면 nil을 반환합니다.
local strVal = "42"
local numVal = tonumber(strVal)

if numVal then
  print("변환된 숫자:", numVal + 8) -- 출력: 변환된 숫자: 50
else
  print("숫자로 변환 실패")
end

nil의 특별한 역할

nil은 단순히 ‘값이 없음’을 의미하는 것을 넘어, 변수 관리에서 특별한 역할을 합니다. 변수에 nil을 할당하는 것은 해당 변수를 사실상 ‘삭제’하는 것과 같은 효과를 가집니다.

local config = { host = "localhost", port = 8080 }

-- config 테이블에서 port 필드를 제거하고 싶을 때
config.port = nil

print(config.port) -- 출력: nil (테이블에 더 이상 port 키가 없음)

더 이상 사용하지 않는 전역 변수에 nil을 할당하면, 해당 변수가 차지하던 메모리를 시스템이 회수할 수 있도록 돕는 좋은 습관입니다.

지금까지 Lua의 변수와 타입 시스템의 핵심적인 내용들을 살펴보았습니다. 데이터를 담는 그릇인 변수, 변수의 유효 범위를 정하는 스코프(특히 local의 중요성), 값에 따라 타입이 유연하게 변하는 동적 타이핑, 그리고 타입을 확인하고 변환하는 방법까지 이해했습니다.

이러한 변수와 타입에 대한 깊이 있는 이해는 여러분이 작성하는 코드의 안정성, 성능, 가독성을 모두 높이는 데 결정적인 역할을 할 것입니다.

루아 Lua 프로그래밍 : 모듈과 패키지 가이드

지금까지 우리는 함수로 코드를 묶고, 테이블로 데이터를 구조화하는 방법을 익혔습니다. 하지만 프로젝트의 규모가 커지기 시작하면, 모든 코드를 단 하나의 파일에 담는 것은 금세 한계에...
eve
53 sec read

루아 (Lua) 프로그래밍: 테이블과 메타테이블의 모든 것

Lua 프로그래밍의 여정에서 가장 중요하고 흥미로운 지점에 도달했습니다. 바로 Lua 언어의 심장이자 가장 중심적인 기능인 테이블(Table)입니다. Lua에는 배열, 딕셔너리, 리스트, 객체 등을 위한 별도의...
eve
1 min read

루아(Lua) 프로그래밍: 제어 구조 조건과 반복

지금까지 우리는 변수에 데이터를 저장하고, 연산자로 이 데이터들을 계산하고 비교하는 방법을 배웠습니다. 하지만 프로그램이 단순히 위에서 아래로 순서대로만 실행된다면, 매우 단순한 작업밖에 할 수...
eve
1 min read