2026년 5월 3일 일요일

Mojo programming language 소개 [기초편]

이번 시간에는 AI 시대를 위해 탄생한 🔥 Mojo programming language에 대해 소개해 보고자 한다. 😎 

AI programming의 새로운 불꽃

def learn_mojo[목차]() raises:
    1. Mojo가 뭐죠 ?
    2. Mojo 개발환경 준비하기
    3. Mojo 기초 문법
        3.1 변수와 타입(Variables and Type)
        3.2 제어문(if, while, for)
        3.3 함수(function)
        3.4 에러 처리
        3.5 소유권(Ownership)과 수명(Lifetime)
        3.6 구조체(struct)
    4. Mojo 고급 문법
        4.1 컴파일 타임 평가(comptime)
        4.2 파라미터화(Parameterization)
        4.3 트레이트(Traits)
        4.4 제네릭(Generics)
        4.5 포인터(Pointer)
    5. GPU Programming 기초
    References


"Mojo가 뭐죠 ? 음... 한마디로 말해, Python 문법을 사용하면서도 C++/Rust/Zig와 같은 성능을 낼 수 있는 compile 언어라고 말할 수 있겠네요. 외관상 느낌은 Python + Rust로 구성된 언어 처럼 보이네요. 근데, 이게 다가 아니에요. Mojo는 C++로 하던 GPU programming을 직접 할 수가 있어요. 그것도 내부에 NVIDIA GPU가 탑재되었든지, AMD GPU가 탑재되었든지 상관하지 않아요. 😍 어떻게 이게 가능할까요 ?"


1. Mojo🔥가 뭐죠?
Python은 언제부턴가 AI programming을 위한 대표 언어가 되었다. 이는 아마도 Python 언어의 간결한 문법과 data 처리에 적합한 구조, 강력한 library 생태계, 그리고 높은 유연성 덕분으로 보인다. 하지만, "Python은 느리다"는 치명적인 단점을 가지고 있는 것도 부인할 수 없는 사실이다. 그렇다면, 이런 생각을 잠시 해보는 것은 어떨까 ? 🐍
  • Python style의 code를 interpreting 방식이 아니라 compiling 방식으로 작성할 수는 없을까 ?
  • Python style의 code가 C++나 Rust처럼 빠를 수는 없을까 ?
  • Python style의 code에 Rust의 소유권 기반 memory safety feature를 추가할 수는 없을까 ?
  • Python style의 code로 C++ 처럼 GPU programming을 직접할 수는 없을까 ? 물론 이와 관련해서는 (programming language는 아니지만) Pytorch, TensorFlow 같은 framework이 있긴하다.

1.1 Mojo 언어 개요
Mojo는 AI 시대를 맞이하여, Python, C++, Rust, Zig의 장점을 취해 만들어진 고성능 system programming & AI 개발 언어다.  LLVM과 Clang, Swift compiler 개발로 유명한 크리스 래트너(Chris Lattner)가 설립한 Modular 사에 현재 개발하고 있는 언어로도 유명하다.

[그림 1-1] mojo programming language [출처 - 참고문헌 1]
📌 둘 중의 하나다. 4가지 언어의 장점을 혼합한 괴물 언어가 만들어지던지, 아니면, 열받아서 터져 버리던지 ㅋㅋ... 정말로 위의 그림대로 된다면, 이걸 안 배울 이유가 있을까 ? 😋

외관상으로 볼때는 Python의 간결한 문법을 사용하고 있지만, 내부적으로는 C++의 강력함(zero cost abstractions, metaprogramming power, low level hardware control 등), Rust의 borrow checker를 이용한 메모리 안정성, 그리고 Zig의 컴파일 시점의 메타프로그래밍(compile-time metaprogramming) 등의 장점을 결합한 compile 언어다. 

한마디로 말해, Python 문법을 사용한 programming을 하면서도 C++/Rust/Zig 같은 성능을 낼 수 있는 system programming 언어라고 말할 수 있겠다.

그런데, 이게 다가 아니다. Mojo는 AI 개발용 언어를 목표로 하고 있다. 따라서 NVIDIA(CUDA), AMD(ROCm) GPU 관련 programming이 가능하도록 설계되어 있다. Python은 AI 연구 분야에서 지배적이지만, 느린 실행 속도 때문에 실제 운영 환경(Production)에서는 C++로 다시 작성해야 하는 번거로움이 있었다. Mojo는 이러한 '프로토타이핑(Python)'과 '실제 배포(C++)' 사이의 간극을 줄이기 위해 만들어졌다.

[그림 1-2] Modular - 새로운 통합 AI 추론 platform(1) [출처 - 참고문헌 10]


[그림 1-3] Modular - 새로운 통합 AI 추론 platform(2) [출처 - 참고문헌 11]
📌 사실상 업계 표준인 vLLM 기반 LLM 서빙 시스템(AI 추론 platform)과 비교하여, Modular(MAX, Mojo)는 좀 더 간결하고, 보다 우수한 성능의 추론 시스템을 목표로 하고 있다.

(이 글을 쓰고 있는 현재 기준으로) 아직은 0.26.3 version 상태로 갈길이 멀어 보이지만, 머지않아 곧 1.0 정식 version을 release할 것으로 믿는다.  Mojo 1.0.0b2(Nightly version) version이 개발 중에 있다. 따라서 조만간 정식 1.0 stable version이 release가 될 것으로 보이는데, 그때가 되면 4개의 언어를 단순히 흉내내는 수준의 모조품으로 전락하게 될 지, 아니면, 단어 뜻(마법의 힘, 사람을 끌어당기는 매력, 또는 누군가를 유능하고 성공적으로 만드는 긍정적인 추진력, 에너지) 그대로 최고의 언어로 탄생할 것인지 결판이 나리라 본다. 😋

1.2 MLIR 기반으로 만들어진 Mojo
MLIR(Multi-Level Intermediate Representation)LLVM 프로젝트의 일환으로 개발된 차세대 컴파일러 인프라이다. 기존의 컴파일러 구조가 가진 한계를 극복하고, 특히 AI 및 하드웨어 가속기 환경에 최적화된 코드를 생성하기 위해 설계되었다.

[그림 1-4] MLIR 기반의 mojo programming language [출처 - 참고문헌 6]

a) MLIR이란 무엇인가 ?
기존의 LLVM IR은 매우 성공적이었지만, 소스 코드와 기계어 사이의 단일 추상화 계층만 제공한다는 한계가 있었다. 이로 인해 TensorFlow나 PyTorch 같은 프레임워크들은 각자 자신들만의 고수준 IR을 따로 만들어야 했고, 이는 파편화와 중복 개발로 이어졌다. MLIR은 이를 해결하기 위해 "여러 계층의 중간 표현을 하나로 통합할 수 있는 프레임워크"를 지향하고 있다.
  1. Dialects (방언): MLIR의 가장 큰 특징으로 특정 도메인(예: 행렬 연산, GPU 코드 생성, 양자 컴퓨팅 등)에 특화된 명령어 집합을 정의할 수 있다.
  2. Lowering (단계적 하향): 고수준의 방언(예: linalg)에서 시작해 중간 수준(예: affine, scf), 그리고 최종적으로 저수준 방언(예: llvm)까지 점진적으로 변환하며 최적화를 수행한다.
  3. 재사용성: 각 단계별로 이미 검증된 최적화 패스(Pass)들을 재사용할 수 있어, 새로운 언어나 하드웨어용 컴파일러를 만드는 비용이 획기적으로 줄어들게 된다.
b) Mojo는 왜 MLIR을 기반으로 만들었을까 ?
Mojo는 Python의 사용성과 C/Rust/Zig 수준의 성능을 동시에 잡으려는 언어인데, 이를 가능하게 하는 핵심 엔진이 바로 MLIR이다.
  1. Python의 한계 극복: Python은 동적 타입 언어라 컴파일 시점에 최적화하기가 매우 어렵다. 반면 Mojo는 MLIR을 통해 코드의 의도를 고수준에서 파악하고, 이를 하드웨어 구조에 맞게 직접 최적화한다.
  2. 이종 하드웨어(Heterogeneous Hardware) 대응: 최근 컴퓨팅 환경은 CPU뿐만 아니라 GPU, TPU, NPU 등 다양한 가속기를 사용하는 추세다. Mojo는 MLIR의 다양한 방언을 활용해 동일한 소스 코드를 서로 다른 하드웨어의 벡터 유닛(SIMD)이나 텐서 코어에 맞게 효율적으로 매핑한다.
  3. 강력한 메타프로그래밍: Mojo는 컴파일 시점에 코드를 생성하고 최적화하는 기능이 강력하다. MLIR의 인프라 덕분에 Mojo 컴파일러는 고수준의 추상화(예: 구조체, 제네릭)를 유지하면서도 최종적으로는 극도로 최적화된 기계어를 뽑아낼 수 있다.
c) MLIR과 Mojo의 시너지 효과
Mojo의 설계자인 크리스 래트너(Chris Lattner)는 LLVM과 MLIR을 모두 만든 인물이다. 그는 Mojo를 설계할 때부터 MLIR을 "언어의 런타임이자 컴파일러 핵심"으로 삼았다.
  1. 계층적 최적화: Mojo 코드는 먼저 Mojo 특유의 방언으로 표현된 후, 루프 최적화에 강한 Affine 방언, 병렬 처리에 강한 GPU 방언 등을 거치며 정교하게 다듬어진다.
  2. 병렬성 및 동시성: MLIR은 데이터 흐름 분석에 최적화되어 있어, Mojo가 멀티코어 환경에서 데이터를 어떻게 효율적으로 분산하고 메모리 계층(L1/L2 캐시)을 활용할지 결정하는 데 도움을 준다.
  3. 컴파일 속도: MLIR은 모듈화가 잘 되어 있어, 거대한 코드베이스도 필요한 부분만 빠르게 다시 컴파일하거나 최적화할 수 있는 구조를 제공한다.


2. Mojo 개발환경 준비하기
먼저 mojo programming 환경을 준비하고, 간단한 예제 코드를 하나 만들어 돌려 보도록 하자.

<Ubuntu 22.04 LTS 환경>
$ curl -fsSL https://pixi.sh/install.sh | sh
-> pixi tool을 내려 받는다. 처음에 한번만 해주면 된다.

$ source ~/.bashrc

<project 만들기>
$ pixi init project3 -c https://conda.modular.com/max-nightly/ -c conda-forge
 -> pixi를 사용하여 project3이라는 디렉토리 아래에 project 환경을 만든다.
✔ Created /mnt/hdd/workspace/Mojo/project3/pixi.toml

$ cd project3

$ pixi add mojo
 -> .pixi 디렉토리 아래에 mojo가 설치된다.
✔ Added mojo >=0.26.3.0.dev2026042105,<0.27

$ ls -la
-rw-rw-r-- 1 chyi chyi   128  4월 22 13:40 .gitattributes
-rw-rw-r-- 1 chyi chyi    47  4월 22 13:40 .gitignore
drwxrwxr-x 3 chyi chyi  4096  4월 22 13:41 .pixi  #이 디렉토리에 mojo가 설치된다.
-rw-rw-r-- 1 chyi chyi 23901  4월 22 13:41 pixi.lock
-rw------- 1 chyi chyi   252  4월 22 13:41 pixi.toml   #Rust의 Cargo.toml과 비슷한 녀석이다.

$ pixi run mojo --version
 -> 설치된 mojo compiler의 version을 확인한다.
Mojo 1.0.0b2.dev2026050306 (dc0cf636)

$ pixi add "mojo==0.26.2"
 -> mojo의 버젼을 특정 버젼으로 변경하려면, 이렇게 해준다.
✔ Added mojo==0.26.2
📌 최신 버젼으로 되돌리고 싶은 경우에는 pixi add "mojo==*" 와 같이 해주면 된다.

$ pixi add "python==3.10.12"
    -> mojo에서 python code를 직접 불러 사용하려는 경우에 자신이 사용하는 python version을 지정하도록 한다.
✔ Added python==3.10.12

# Edit the first.mojo file with VSCode
 -> VSCode 상태에서 first.mojo라는 파일을 하나 연 후, 아래 내용을 입력한다.

[그림 2-1] first.mojo 파일 편집하기

$ pixi shell
 -> virtual 환경을 시작하도록 한다. 이 명령을 실행해 주어야, mojo compiler를 실행할 수가 있다.
(project3) chyi@earth:~/Mojo/project3$ 
(project3) chyi@earth:~/Mojo/project3$ mojo first.mojo 
Who are you? michael
Hi, michael!

[그림 2-2] first.mojo build 및 실행하기

이번 posting에서 소개하는 Mojo 예제 코드는 대부분 아래 site의 내용을 기초로 하였다.



3. Mojo의 기초 문법
Mojo는 파이썬(Python)의 문법과 사용성을 그대로 가져가면서도, C/C++, Rust & Zig 수준의 성능과 메모리 안전성을 내기 위해 몇 가지 고유한 문법을 추가한 형태라고 하였다.

3.1 변수와 타입(Variables and Type)
Mojo에서는 파이썬처럼 변수를 그냥 쓸 수도 있지만, 성능과 안전성을 위해 타입을 명시하여 선언하는 것을 강력히 권장한다.

[코드 3-1] 명시적인 타입 및 암시적인 타입 예제 - variables.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
def variables_example():
var name: String = "Mojo" # 타입 명시 (권장)
var age = 1 # 타입 추론 (Int로 인식됨)
print("Age:", age)
age = 2 # var로 선언했으므로 값 변경 가능
comptime PI = 3.14159 # 컴파일 타임 상수
# PI = 3.14 <- 에러 발생! (comptime은 한 번 정해지면 변경 불가)

print("Name:", name)
print("Age:", age)
print("PI:", PI)

def main():
#변수 선언과 초기화
#명시적인 타입 지원 시 var 키워드 사용
var greeting: String = "Hello World"
var a: Int = 10
var b: Float64 = 3.14
var c: Bool = True
print(a)
print(b)
print(c)
print(greeting)

#암시적인 타입 지원 - Python과 유사하게 타입 추론이 가능
d = 20
e = 2.718
f = False
g = "Goodbye"
print(d)
print(e)
print(f)
print(g)

variables_example()
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build variables.mojo
(project3) chyi@earth:~/Mojo/project3$ ./variables 
10
3.14
True
Hello World
20
2.718
False
Goodbye
Age: 1
Name: Mojo
Age: 2
PI: 3.14159
------------------------------------------------------------------------------------------------------------ 🔥 

3.2 제어문(if, while, for)
Mojo의 제어문(if, while loop, for loop)은 python의 문법과 크게 다르지 않다. 

[코드 3-2] if 문과 while 문 사용 예제 - control_flow.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# 제어 흐름 예시: 조건문과 반복문
def main() raises:
temp_celsius = Float64(25)
# 조건문 예시
if temp_celsius > 20:
print("It is warm.")
print("The temperature is", temp_celsius * 9 / 5 + 32, "Fahrenheit." )
# elif와 else 예시
temp_celsius = 25
if temp_celsius <= 0:
print("It is freezing.")
elif temp_celsius < 20:
print("It is cool.")
elif temp_celsius < 30:
print("It is warm.")
else:
print("It is hot.")

# 삼항 연산자 예시
temp_celsius = 15
forecast = "warm" if temp_celsius > 20 else "cool"
print("The forecast for today is", forecast)

# 반복문 예시: 피보나치 수열 출력
fib_prev = 0
fib_curr = 1

print(fib_prev, end="")
while fib_curr < 50:
print(",", fib_curr, end="")
fib_prev, fib_curr = fib_curr, fib_prev + fib_curr
print()
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build control_flow.mojo
(project3) chyi@earth:~/Mojo/project3$ ./control_flow 
It is warm.
The temperature is 77.0 Fahrenheit.
It is warm.
The forecast for today is cool
0, 1, 1, 2, 3, 5, 8, 13, 21, 34
------------------------------------------------------------------------------------------------------------ 🔥 

[코드 3-3] for 문 사용 예제 - control_flow2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.collections import Set

# 리스트, 집합, 딕셔너리 등 다양한 컬렉션 타입에서 for 루프 사용 가능
def main() raises:
# 리스트 예시
states = ["California", "Hawaii", "Oregon"]
for state in states:
print(state)

# 집합 예시
values = {42, 0}
for item in values:
print(item)

# 딕셔너리 예시
var capitals: Dict[String, String] = {
"California": "Sacramento",
"Hawaii": "Honolulu",
"Oregon": "Salem"
}

# 딕셔너리에서 키를 순회
for var state in capitals:
print(capitals[state] + ", " + state)

# 딕셔너리에서 키와 값을 동시에 순회
for item in capitals.items():
print(item.value + ", " + item.key)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build control_flow2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./control_flow2 
California
Hawaii
Oregon
42
0
Sacramento, California
Honolulu, Hawaii
Salem, Oregon
Sacramento, California
Honolulu, Hawaii
Salem, Oregon
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 3-4] for 문을 사용한 List, Dictionary 순회 예제  - control_flow3.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.collections import List
# Python과의 상호 운용을 위한 모듈 임포트
from std.python import Python

# for-else 구문과 Python 리스트 순회 예시
def main() raises:
# for-else 구문 예시
var empty: List[Int] = []
for i in empty:
print(i)
else:
print("Finished executing 'for' loop")

# for-else 구문에서 else 블록은 루프가 정상적으로 종료될 때 실행됩니다.
# 루프가 break로 중단되면 else 블록은 실행되지 않습니다.
animals = ["cat", "aardvark", "hippopotamus", "dog"]
for animal in animals:
if animal == "dog":
print("Found a dog")
break
else:
print("No dog found")

# Mojo의 리스트는 단일 타입만 허용하지만, Python 리스트는 다양한 타입을 포함할 수 있습니다.
py_list = Python.list(42, "cat", 3.14159)
for py_obj in py_list: # Each element is of type "PythonObject"
print(py_obj)

# Python 딕셔너리 순회 예시
py_dict = Python.evaluate("{'a': 1, 'b': 2.71828, 'c': 'sushi'}")
for py_key in py_dict: # Each key is of type "PythonObject"
print(py_key, py_dict[py_key])

# Python 딕셔너리 순회 예시 (items() 사용)
py_dict = Python.evaluate("{'a': 1, 'b': 2.71828, 'c': 'sushi'}")
for py_tuple in py_dict.items(): # Each 2-tuple is of type "PythonObject"
print(py_tuple[0], py_tuple[1])
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build control_flow3.mojo
(project3) chyi@earth:~/Mojo/project3$ ./control_flow3
Finished executing 'for' loop
Found a dog
42
cat
3.14159
a 1
b 2.71828
c sushi
a 1
b 2.71828
c sushi
------------------------------------------------------------------------------------------------------------ 🔥 

3.3 함수(functions)
(v26.2 스펙 통일 기준 이후) 함수는 def 키워드를 사용해 선언한다. 파이썬처럼 동적으로 대충(?) 쓸 수도 있지만, 아래처럼 매개변수와 반환 타입을 명확히 지정해주면 Mojo 컴파일러가 코드를 엄청나게 빠른 기계어로 최적화해 준다.

def function_name[ ​ parameters ... ]( ​ arguments ... ) -> return_value_type: ​ function_body

이 절에서는 [ parameters ... ]가 없는 함수(기존 python 함수 형태) 예제를 먼저 소개하기로 한다.


[코드 3-5] function 사용 예제(1) - function.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# 매개변수 타입(Int)과 반환 타입(-> Int)이 명시된 함수
def add_numbers(a: Int, b: Int) -> Int:
var result = a + b
return result

# 매개변수 타입(String)과 반환 타입(None)이 명시된 함수
def greet(name: String) -> None:
print("Hello,", name)

# 매개변수 타입(Int)과 반환 타입(Int)이 명시된 함수, exp는 기본값 2로 설정
def my_pow(base: Int, exp: Int = 2) -> Int:
return base ** exp

# 가변 인자(*values: Int)와 반환 타입(Int)이 명시된 함수
def sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum + value
return sum

# main 함수에서 위에서 정의한 함수를 호출하여 결과를 출력
def main():
sum_result = add_numbers(5, 7)
print("Sum:", sum_result)

greet("Mojo")

# 기본값을 사용하여 제곱 계산
var z = my_pow(3)
print(z)

# 키워드 인수 사용 예시
z = my_pow(exp=3, base=2)
print(z)

sum_all = sum(1, 2, 3, 4, 5)
print("Sum of all:", sum_all)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build function.mojo
(project3) chyi@earth:~/Mojo/project3$ ./function 
Sum: 12
Hello, Mojo
9
8
Sum of all: 15
------------------------------------------------------------------------------------------------------------ 🔥 

함수에 인자를 넘길 때, 파이썬처럼 무조건 참조로 넘기는 것이 아니라 목적에 따라 명시적으로 키워드를 지정할 수 있다. 함수에 인자를 넘길 경우, copy(복사), reference(대여), move(이동) 중의 하나가 일어나게 된다. 함수 선언시 사용되는 명시적 keyword의 용도를 정리해 보면 다음과 같다.
  • read: 값을 읽기만 할 때 사용한다. 해당 argument를 immutabe reference로 넘긴다는 의이미다. read keyword는 보통 생략한다. C++의 const&와 유사하다.
  • mut: 넘겨받은 원본 변수의 값을 함수 안에서 직접 수정해야 할 때 사용한다. 해당 argument를 mutable reference로 넘긴다는 의미이다. Rust의 mut와 동일한 개념이다.
  • var: 값의 소유권 자체를 함수로 완전히 넘겨받을 때 사용한다(이동 혹은 복사에 의한 소유권 획득). Caller에서 ^를 사용하여 소유권 이동을 시킬 수도 있고, ^를 사용하지 않을 경우, copy가 발생할 수도 있다.
  • out: 생성자(__init__()) 등에서 아직 초기화되지 않은 빈 공간을 받아 채워 넣을 때 사용한다.
  • deinit: 소멸자(__del__()), move constructor 등에서 사용된다. function 시작 시 해당 argument가 초기화되고, return 시 해제되는 것을 의미할 때 사용된다(해당 argument가 제거될 거라는 것을 표시하는 것임).

[코드 3-6] function 사용 예제(2) - function2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# v0.26.x 기준 최신 문법
def my_function(
a: String, # 1. 생략됨: 기본적으로 'read' (읽기 전용)로 동작
read b: String, # 2. 명시적 표기: 위 a와 100% 동일하게 동작함
mut c: String, # 3. 원본 수정: 반드시 'mut' 명시 필요
var d: String, # 4. 소유권 획득: 반드시 'var' 명시 필요
var e: String # 5. 소유권 획득 + 원본 수정: 반드시 'var' 명시 필요
):
# a += "!" <- 컴파일 에러! (기본적으로 수정 불가)
# b += "!" <- 컴파일 에러! (읽기 전용)
c += " world" # 성공: 넘겨받은 원본 문자열 데이터 자체가 변경됨
d += " world" # 성공: 나만의 소유/복사본이므로 마음대로 변경 가능 (원본은 영향 없거나 파괴됨)
e += " world" # 성공: 나만의 소유/복사본이므로 마음대로 변경 가능 (원본은 영향 없거나 파괴됨)

def main():
original1 = "Hello1"
original2 = "Hello2"
original3 = "Hello3"
original4 = "Hello4"
original5 = "Hello5"
my_function(original1, original2, original3, original4, original5^)

# 원본은 변경되지 않음 (a는 읽기 전용)
print("Original after function call:", original1)
# 원본은 변경되지 않음 (b는 읽기 전용)
print("Original after function call:", original2)
# 원본이 수정됨 (c는 원본이므로)
print("Original after function call:", original3)
# 원본이 변경되지 않음 (d는 소유권 획득하여 복사본이므로)
print("Original after function call:", original4)
# 원본의 소유권이 이동하여 더 이상 접근 불가
#print("Original after function call:", original5)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build function2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./function2 
Original after function call: Hello1
Original after function call: Hello2
Original after function call: Hello3 world
Original after function call: Hello4
------------------------------------------------------------------------------------------------------------ 🔥 

3.4 에러 처리
Mojo의 에러 처리 방식을 알아 보도록 하자. 함수 끝에 raises라는 keyword를 추가하는 부분이 주목할만한 점이다. 나머지는 Python과 대동소이하다.

try:
# Code that might raise an error
except e:
# Runs if an error occurs
else:
# Runs if no error occurs
finally:
# Always runs, regardless of outcome


[코드 3-7] 에러 처리 예제 - error_handling.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
def process_record(id: Int) raises -> String:
if id < 0:
raise Error("invalid record ID: must be non-negative")
if id > 999:
raise Error("record not found")
return String("record_", id)

# exception이 발생할 수 있는 코드 블록을 try로 감싸고,
# 예외를 처리하는 except 블록과, 예외가 발생하지 않았을 때 실행되는 else 블록,
# 그리고 항상 실행되는 finally 블록을 포함하는 예시
def main() raises:
# 외부에서 발생한 예외를 잡아서 처리하는 최상위 try 블록
try:
for id in [5, 0, 1001, -3, 42]:
var result: String
# 내부에서 발생한 예외를 잡아서 처리하는 try 블록
try:
print()
print("try => id:", id)
if id == 0:
continue
result = process_record(id)
# except 블록에서 예외 객체 e를 검사하여 특정 메시지를 포함하는 경우에는
# fatal error로 간주하여 예외를 다시 발생시키고, 그렇지 않은 경우에는
# handled error로 간주하여 예외를 처리하는 예시
except e:
if "invalid" in String(e):
print("except => fatal:", e)
raise e^
print("except => handled:", e)
# else 블록은 try 블록에서 예외가 발생하지 않았을 때 실행됩니다.
else:
print("else => success:", result)
# finally 블록은 예외 발생 여부와 상관없이 항상 실행됩니다.
finally:
print("finally => done with id:", id)
# 외부에서 발생한 예외를 잡아서 처리하는 except 블록
except e:
print("\nre-raised error:", e)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build error_handling.mojo
(project3) chyi@earth:~/Mojo/project3$ ./error_handling 

try     => id: 5
else    => success: record_5
finally => done with id: 5

try     => id: 0
finally => done with id: 0

try     => id: 1001
except  => handled: record not found
finally => done with id: 1001

try     => id: -3
except  => fatal: invalid record ID: must be non-negative
finally => done with id: -3

re-raised error: invalid record ID: must be non-negative

------------------------------------------------------------------------------------------------------------ 🔥 

3.5 소유권(Ownership)과 수명(Lifetime)
Mojo의 메모리 세계에서는 모든 데이터(값)가 반드시 단 하나의 소유자(Owner)를 가진다(Rust의 소유권 개념과 동일하다).
  • 대여 (Borrowing - read, mut): 내가 가진 데이터의 소유권은 그대로 유지한 채, 다른 함수나 변수에게 잠시 빌려주는 것(reference를 의미함)을 말한다. 앞서 언급한 대로 값을 읽기만 할 거면 read(또는 생략), 값을 수정하게 허락할 거면 mut를 사용한다. (Rust의 경우와 동일하게) Int, Float, Bool 등 기본 data type(register-passable trivial type)의 경우는 예외적으로 copy가 이루어진다.

  • 이전 (Move - ^ 연산자): 데이터의 진짜 주인을 아예 다른 곳으로 넘겨버리는 것을 말한다. 소유권을 넘겨주고 나면, 원래 주인(변수)은 그 데이터를 더 이상 쓸 수 없는 빈 껍데기가 된다. 이때 넘겨받는 쪽의 함수는 매개변수에 반드시  var를 명시하여 "내가 이제 새 주인이다"라고 선언해야 한다. 실제 Move 동작은 stack 변수를 복사하는 게 전부이다. 다시말해 heap 영역을 복사하지는 않는다(그래서 빠르다).


[그림 3-1] 소유권 개념(1) - 얕은 복사(Shallow Copy)


[그림 3-2] 소유권 개념(2) - 깊은 복사(Deep Copy)


[그림 3-3] 소유권 개념(3) - 소유권 빌림(Borrowing)(= 참조)


[그림 3-4] 소유권 개념(4) - 소유권 이동(Move)


[코드 3-8] 소유권 사용 예제(1) - ownership.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# read 키워드(생략 가능)는 함수 매개변수에서 사용되어 해당 매개변수가
# 읽기 전용임을 나타냅니다. 즉, 함수 내에서 해당 매개변수의 값을 변경할 수 없습니다.
def print_list(read list: List[Int]):
for item in list:
print(item)
print()

# mut 키워드는 함수 매개변수에서 사용되어 해당 매개변수가
# 가리키는 원본 데이터를 수정할 수 있음을 나타냅니다.
def mutate(mut l: List[Int]):
l.append(5)

# var 키워드는 함수 매개변수에서 사용되어 해당 매개변수의 소유권을 획득하여
# 복사본을 만들어 함수 내에서 자유롭게 수정할 수 있음을 나타냅니다
def take_text(var text: String):
text += "!"
print(text)

def main():
var values = [1, 2, 3, 4]
print_list(values)

mutate(values)
print_list(values)

var message = "Hello" # Create a run-time String value
take_text(message) # message의 소유권을 이동하지 않고 그냥 전달
print(message)

message = "Hello" # Create a run-time String value
take_text(message^) # ^를 붙여서 message의 소유권을 이동
#print(message) # error: use of uninitialized value 'message'
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build ownership.mojo
(project3) chyi@earth:~/Mojo/project3$ ./ownership
1
2
3
4

1
2
3
4
5

Hello!
Hello
Hello!
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 3-9] 소유권 사용 예제(2) - ownership2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
struct MyData:
var value: Int
def __init__(out self, v: Int):
self.value = v

# var를 명시하여 소유권을 완전히 넘겨받는 함수
def take_ownership(var data: MyData):
data.value += 100
print("새 주인이 값을 변경함:", data.value)
# 이 함수가 끝나면 data는 메모리에서 파괴됩니다.

def main():
var my_obj = MyData(10) # my_obj가 데이터의 최초 소유자

# ^ (Transfer) 연산자를 사용하여 소유권을 완전히 넘김
take_ownership(my_obj^)

# 컴파일 에러 발생!
# my_obj는 이미 소유권을 넘겨주었으므로 더 이상 접근할 수 없습니다.
# print(my_obj.value)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build ownership2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./ownership2
새 주인이 값을 변경함: 110
------------------------------------------------------------------------------------------------------------ 🔥 

다른 언어들과 비교했을 때 Mojo 수명 관리의 가장 큰 특징은 ASAP(가능한 한 빨리) 메모리 해제 기능이다.
  • C++ / Rust: 변수가 선언된 블록({ } 또는 함수 스코프)이 끝날 때 메모리가 해제된다.
  • Python: 참조 카운트가 0이 되거나 가비지 컬렉터가 나중에 여유가 있을 때 알아서 지운다.
  • Mojo (ASAP 규칙): 코드를 분석하여 해당 변수가 마지막으로 사용된 직후에 즉시(ASAP) 메모리를 해재한다. 즉, 블록이 끝날 때까지 기다리지 않는다. 이 규칙 덕분에 Mojo는 메모리 사용량을 최소한으로 유지하고 캐시 효율을 극대화시켜준다.

3.6 구조체(Struct)
struct 즉, 구조체는 Mojo 문법의 꽃이라고 할 수 있다. 파이썬의 class도 지원하지만, 고성능 메모리 제어를 위해 Mojo 개발자들은 주로 struct를 사용한다.  struct는 런타임에 동적으로 속성을 추가할 수 없는 대신, 메모리에 아주 빽빽하고 빠르게 저장된다.

[코드 3-10] struct 예제(1) - structs.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# 구조체 정의와 사용 예시
struct MyPair:
var first: Int
var second: Int

# 구조체의 필드를 초기화하는 생성자 메서드
def __init__(out self, first: Int, second: Int):
self.first = first
self.second = second

# @fieldwise_init 데코레이터를 사용하여 구조체의 필드를 자동으로 초기화하는 예시
@fieldwise_init
struct MyPair2:
var first: Int
var second: Int

def main() raises:
# MyPair 구조체의 인스턴스를 생성하고 필드에 접근하는 예시
pair = MyPair(1, 2)
print("First:", pair.first)
print("Second:", pair.second)

# MyPair2는 @fieldwise_init 덕분에 생성자 메서드를 명시적으로 정의하지 않아도 됩니다.
pair2 = MyPair2(1, 2)
print("First:", pair2.first)
print("Second:", pair2.second)
------------------------------------------------------------------------------------------------------------ 🔥 
📌 @fieldwise_init decorator가 자동으로 __init__( ) 생성자를 만들어 준다.

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build structs.mojo
(project3) chyi@earth:~/Mojo/project3$ ./structs 
First: 1
Second: 2
First: 1
Second: 2
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 3-11] struct 예제(2) - structs2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
@fieldwise_init
struct MyStruct:
var value: Int

# increment 메서드는 self를 mutable로 받아서 구조체의 필드를 수정할 수 있습니다.
def increment(mut self):
self.value += 1

@fieldwise_init
struct MyPair(ImplicitlyCopyable):
var first: Int
var second: Int
def main():
# 1) MyStruct의 인스턴스를 생성하고 increment 메서드를 호출하여
# value 필드가 증가하는 것을 확인하는 예시
var s: MyStruct = MyStruct(10)
print("Before increment:", s.value) # Output: 10
s.increment()
print("After increment:", s.value) # Output: 11

# 2) MyPair 구조체의 인스턴스를 생성하고 필드에 접근하는 예시
var a = MyPair(1, 2)
print("Original MyPair:", a.first, a.second) # Output: 1 2

# Implicit copy(암묵적인 복사)
var b = a # value of type 'MyPair' cannot be implicitly copied,
# it does not conform to 'ImplicitlyCopyable'
print("Copied MyPair:", b.first, b.second) # Output: 1 2

# Explicit copy(명시적인 복사)
var c = a.copy() # value of type 'MyPair' cannot be implicitly copied,
# it does not conform to 'ImplicitlyCopyable'
print("Explicitly Copied MyPair:", c.first, c.second) # Output: 1 2

# Move (이동)
var d = a^ # value of type 'MyPair' cannot be copied or moved; consider
# conforming it to 'Copyable', which also adds 'Movable'
# conformance.
print("Moved MyPair:", d.first, d.second) # Output: 1 2
------------------------------------------------------------------------------------------------------------ 🔥 
📌 ImplitcitlyCopyable trait 덕분에 암묵적인 복사가 가능하다.

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build structs2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./structs2 
Before increment: 10
After increment: 11
Original MyPair: 1 2
Copied MyPair: 1 2
Explicitly Copied MyPair: 1 2
Moved MyPair: 1 2
------------------------------------------------------------------------------------------------------------ 🔥

[코드 3-12] struct 예제(3) - destructor_asap.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
@fieldwise_init
struct Balloon(Writable):
var color: String

# write_to 메서드는 self를 mutable로 받아서 구조체의 필드를 읽어와서 writer에 쓸 수 있습니다.
def write_to(self, mut writer: Some[Writer]):
writer.write(String("a ", self.color, " balloon"))

# __del__ 메서드는 self를 deinit으로 받아서 구조체가 소멸될 때 실행되는 코드를 정의할 수 있습니다.
def __del__(deinit self):
print("Destroyed", String(self.color))


# main 함수에서는 Balloon 구조체의 인스턴스를 생성하고,
# 이를 출력하여 __del__ 메서드가 언제 호출되는지 확인할 수 있습니다.
def main():
var a = Balloon("red")
var b = Balloon("blue")
print(a)
# a.__del__() runs here for "red" Balloon

a = Balloon("green")
print(a)
# a.__del__() runs immediately because "green" Balloon is never used

print(b)
# b.__del__() runs here

------------------------------------------------------------------------------------------------------------ 🔥 
📌 Mojo에서는 변수의 scope가 끝나는 시점이 아니라, 변수가 더 이상 사용되지 않을 경우에 곧 바로 해당 변수가 소멸되는 구조라고 하였다.

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build destructor_asap.mojo
(project3) chyi@earth:~/Mojo/project3$ ./destructor_asap 
a red balloon
Destroyed red
a green balloon
Destroyed green
a blue balloon
Destroyed blue
------------------------------------------------------------------------------------------------------------ 🔥 


4. Mojo의 고급 문법
이제부터는 meta programming, trait, generic, pointer 등 좀 난해한 얘기를 해야 할 차례이다. 모든 내용을 설명하기 보다는 중요한 내용을 중심으로 소개하도록 한다.

4.1 컴파일 타임 평가(comptime)
Mojo에는 comptime이라는 강력한 키워드가 있다. 이 키워드가 붙은 변수나 제어문은 프로그램이 실행되기도 전에 컴파일러가 먼저 계산해 버린다. C++의 constexpr과 유사한 개념으로 볼 수 있는데, 사실은 Zig에서 그 개념을 빌려왔다.

[코드 4-1] comptime 예제 - comptime.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# comptime 키워드를 사용하여 컴파일 타임 상수를 선언할 수 있습니다.
# 컴파일 타임 상수는 프로그램이 컴파일될 때 값이 결정되고,
# 런타임에는 변경할 수 없는 값을 의미합니다.
comptime VALUE = 10

def scope_me():
print(VALUE) # prints 10
comptime VALUE = 20
# comptime VALUE = 30 # error: invalid redeclaration of VALUE
comptime if True:
comptime VALUE = 40
print(VALUE) # prints 40
print(VALUE) # prints 20

def main():
scope_me()
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build comptime.mojo
(project3) chyi@earth:~/Mojo/project3$ ./comptime
10
40
20
------------------------------------------------------------------------------------------------------------ 🔥 

4.2 Parameterization(파라미터화)
메타프로그래밍(Metaprogramming)이라는 단어 자체가 "코드를 작성하는 코드"를 뜻하다 보니, 처음 접하면 C++이나 C의 매크로(Macros)처럼 복잡하고 난해하게 느껴질 수 있다. 하지만 Mojo의 메타프로그래밍은 "런타임(프로그램 실행 중)에 할 연산을 컴파일 타임(프로그램 번역 중)으로 앞당겨서 처리한다"는 핵심 원리만 이해하면 훨씬 접근하기 쉽다. 별도의 복잡한 매크로 언어를 새로 배울 필요 없이, 기존의 친숙한 Pythonic 문법을 거의 그대로 컴파일 타임에 사용할 수 있도록 직관적으로 설계되었기 때문이다.

Mojo 함수가 입력을 받는 방식은 크게 두 가지로 나뉜다.
  • 인자 (Arguments - ( ) 사용): 일반적인 프로그래밍 언어들처럼 프로그램이 실행될 때(Runtime) 결정되는 값이다.
  • 매개변수 (Parameters - [ ] 사용): 프로그램이 컴파일될 때(Compile-time) 미리 결정되는 값이다.

[코드 4-2] parameterization 예제 - parameter.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
def multiplier[factor: Int](x: Int) -> Int:
return x * factor

def main():
comptime times_ten = multiplier[10]
x10 = times_ten(3)
print(x10) # prints 30
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build parameter.mojo
(project3) chyi@earth:~/Mojo/project3$ ./parameter
10
40
20
------------------------------------------------------------------------------------------------------------ 🔥 

함수의 파라미터 문법은 다음과 같이 좀 복잡해 보인다.

def my_sort[
# infer-only parameters
dtype: DType,
width: Int,
//,
# positional-only parameter
values: SIMD[dtype, width],
/,
# positional-or-keyword parameter
compare: def(Scalar[dtype], Scalar[dtype]) thin -> Int,
*,
# keyword-only parameter
reverse: Bool = False,
]() -> SIMD[dtype, width]:

  • 이중 슬래시(//)이중 슬래시 앞에 선언된 매개변수는 추론 전용 매개변수이다.
  • 슬래시(/) 슬래시 앞에 선언된 매개변수는 위치 전용 매개변수이다. 위치 전용 매개변수와 키워드 전용 매개변수는 위치 전용 인수와 키워드 전용 인수와 동일한 규칙을 따른다.
  • *Types와 같이 별표(*)로 시작하는 매개변수 이름은 가변 인자 매개변수를 나타낸다(위 예시에는 표시되지 않음). 가변 인자 매개변수 다음에 오는 모든 매개변수는 키워드 전용이다.
  • 별표(*) 가변 인자 매개변수가 없는 매개변수 목록에서 별표만 있는 경우, 뒤따르는 매개변수는 키워드 전용 매개변수임을 나타낸다.
  • 등호(=) : 선택적 매개변수에 기본값을 지정해준다.

4.3 트레이트(Traits)
Rust의 Trait(트레이트)를 이미 알고 있다면, Mojo의 Trait 개념도 90% 이상 이해한 거나 다름없다. Mojo의 Trait 역시 Rust와 마찬가지로 "이 타입(Struct)은 반드시 이러한 행동(메서드)들을 할 수 있어야 해!"라고 규정하는 '계약서(Interface)' 역할을 한다. 제네릭([])을 사용할 때 아무 타입이나 들어오지 못하게 막는 훌륭한 문지기 역할을 하는 용도로 사용된다. 

[코드 4-3] trait 예제(1) - trait.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# 트레이트는 구조체가 특정 행동을 구현하도록 강제하는 일종의 인터페이스입니다.
trait Quackable:
def quack(self):
pass

@fieldwise_init
# Copyable 트레이트는 구조체가 복사 가능하다는 것을 나타냅니다.
# Quackable 트레이트는 구조체가 quack 메서드를 구현해야 한다는 것을 나타냅니다.
struct Duck(Copyable, Quackable):
def quack(self):
print("Quack")

@fieldwise_init
struct StealthCow(Copyable, Quackable):
def quack(self):
print("Moo!")

# main 함수에서는 Duck과 StealthCow 구조체의 인스턴스를 생성하고,
# quack 메서드를 호출하여 트레이트가 제대로 구현되었는지 확인할 수 있습니다.
def main():
var duck = Duck()
var cow = StealthCow()
duck.quack() # Output: Quack
cow.quack() # Output: Moo!
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build trait.mojo
(project3) chyi@earth:~/Mojo/project3$ ./trait
Quack
Moo!
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 4-4] trait 예제(2) - trait2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# Quackable 트레잇은 구조체가 quack 메서드를 구현해야 한다는 것을 나타냅니다.
trait Quackable:
def quack(self):
pass

# Flyable 트레잇은 구조체가 fly 메서드를 구현해야 한다는 것을 나타냅니다.
trait Flyable:
def fly(self): ...

# quack_and_go 함수는 Quackable과 Flyable 트레잇을 모두 만족하는 타입을 인자로 받아서,
# quack과 fly 메서드를 호출합니다.
# Trait composition을 사용하여 두 트레잇을 모두 만족하는 타입을 요구.
def quack_and_go[type: Quackable & Flyable](quacker: type):
quacker.quack()
quacker.fly()

@fieldwise_init
# FlyingDuck 구조체는 Quackable과 Flyable 트레잇을 모두 구현합니다.
struct FlyingDuck(Copyable, Quackable, Flyable):
def quack(self):
print("Quack")

def fly(self):
print("Whoosh!")

# main 함수에서는 FlyingDuck 구조체의 인스턴스를 생성하고,
# quack_and_go 함수를 호출하여 트레잇이 제대로 구현되었는지 확인할 수 있습니다.
def main():
var duck = FlyingDuck()
quack_and_go(duck) # Output: Quack \n Whoosh!
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build trait2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./trait2
Quack
Whoosh!
------------------------------------------------------------------------------------------------------------ 🔥 

사용자가 정의한 트레이트도 당연히 중요하지만, 실제로는 (아래에 열거되어 있는 것과 같이) Mojo의 built-in trait를 제대로 활용할 수 있어야 한다.


이 중에서 Copyable과 Movable 트레이트만 잠시 살펴 보기로 하자.

파이썬에서는 a = b라고 쓰면 단순히 메모리 주소(참조)만 복사된다. 하지만 Mojo(또는 Rust) 같은 시스템 프로그래밍 언어에서 a = b는 실제 힙(Heap) 메모리에 있는 거대한 데이터를 통째로 복사(Deep Copy)해야 할 수도 있는 무거운 작업에 해당한다. 따라서 Mojo 컴파일러는 "개발자가 명시적으로 허락하지 않은 구조체(Struct)는 절대 함부로 복사하거나 이동시키지 않는다"는 엄격한 원칙을 가진다. 이를 허락해 주는 인증 마크가 바로 CopyableMovable 트레이트이다.

a) Copyable (복사 가능 인증)
  • 의미: "이 구조체는 원본을 훼손하지 않고 똑같은 복사본을 만들어낼 수 있다."
  • 조건: 이 마크를 달려면 구조체 안에 반드시 def __init__(out self,*, copy: Self) 이라는 복사 생성자 메서드를 구현해야 한다.
  • 언제 쓰이나: 변수를 다른 변수에 할당(a = b)하거나, 함수에 인자를 var로 넘길 때 컴파일러가 알아서 생성자를 호출한다.
b) Movable (소유권 이동 가능 인증)
  • 의미: "이 구조체는 복사라는 무거운 작업 없이, 소유권 자체를 다른 곳으로 가볍게 넘겨줄 수 있다."
  • 조건: 이 마크를 달려면 구조체 안에 반드시 def __init__(out self, *, deinit take: Self) 이라는 이동 생성자 메서드를 구현해야 한다.
  • 언제 쓰이나: 데이터의 덩치가 커서 복사하기 싫을 때, 이전 연산자(^)를 써서 a = b^ 처럼 소유권을 아예 넘겨버릴 때 사용된다.

[코드 4-5] trait 예제(3) - trait3.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# 괄호 안에 "나는 Copyable과 Movable 계약을 따르겠다"고 선언합니다.
#struct MyData(Copyable, ImplicitlyCopyable, Movable):
struct MyData(ImplicitlyCopyable, Movable):
#struct MyData(Copyable, Movable):
var value: Int
var str: String

# 기본 생성자
def __init__(out self, v: Int, s: String):
self.value = v
self.str = s

def main():
var a = MyData(10, "Hello, World!")

# 1. ImplicitlyCopyable 덕분에 가능해진 작업
var b = a # 여기서 내부적으로 def __init__(out self,*, copy: Self)가 자동 호출됨
print("a.value:", a.value) # Output: a.value: 10
print("b.value:", b.value) # Output: b.value: 10
print("a.str:", a.str) # Output: a.str: Hello, World!
print("b.str:", b.str) # Output: b.str: Hello, World!

# 2. Copyable(혹은 ImplicitlyCopyable) 덕분에 가능해진 작업
var c = a.copy() # 명시적으로 copy 메서드 호출
print("a.value:", a.value)
print("c.value:", c.value) # Output: c.value: 10
print("a.str:", a.str) # Output: a.str: Hello, World!
print("c.str:", c.str) # Output: c.str: Hello, World!

# 3. Movable 덕분에 가능해진 작업
var d = a^ # 여기서 내부적으로 def __init__(out self, *, deinit take: Self)가 자동 호출됨
print("d.value:", d.value) # Output: d.value: 10
print("d.str:", d.str) # Output: d.str: Hello, World!
# print("a.value:", a.value) # 컴파일 에러 발생! a는 이미 이동되었으므로 접근 불가
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build trait3.mojo
(project3) chyi@earth:~/Mojo/project3$ ./trait3
a.value: 10
b.value: 10
a.str: Hello, World!
b.str: Hello, World!
a.value: 10
c.value: 10
a.str: Hello, World!
c.str: Hello, World!
d.value: 10
d.str: Hello, World!
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 4-6] trait 예제(4) - trait4.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
@fieldwise_init
# Point 구조체는 Writable 트레잇을 구현하여,
# write_to 메서드를 통해 자신의 데이터를 출력할 수 있습니다.
struct Point(Writable):
var x: Float64
var y: Float64

# write_to 메서드는 self를 mutable로 받아서 구조체의 필드를 읽어와서
# writer에 쓸 수 있습니다.
def write_to(self, mut writer: Some[Writer]):
writer.write(t"Point({self.x}, {self.y})")

# main 함수에서는 Point 구조체의 인스턴스를 생성하고,
# 이를 출력하여 write_to 메서드가 제대로 작동하는지 확인할 수 있습니다
def main():
var p = Point(1.5, 2.7)
print(p) # Point(1.5, 2.7)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build trait4.mojo
(project3) chyi@earth:~/Mojo/project3$ ./trait4
Point(1.5, 2.7)
------------------------------------------------------------------------------------------------------------ 🔥 

4.4 Generic(제네릭)
제네릭은 쉽게 말해 "데이터의 타입(Type)을 미리 정하지 않고, 나중에 쓸 때 정하도록 뚫어놓은 구멍(Placeholder)"이다. C++의 템플릿(Template)이나 Rust, Java의 제네릭과 목적이 완전히 같다. 앞서 배운 Trait(트레이트)와 결합할 때 제네릭의 진짜 위력이 나오게 된다.

[코드 4-7] generics 예제(1) - generic.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# T는 "뭔지 모르겠지만 어떤 타입"을 의미합니다.
# AnyType은 "아무 타입이나 다 올 수 있다"는 가장 기본적인 제약 조건입니다.
# Writable은 "값을 출력할 수 있다"는 제약 조건입니다.
def print_twice[T: Writable](val: T):
# 컴파일러가 T에 무엇이 들어올지 나중에 알아서 채워 넣습니다.
print(val, val) # Writable 트레잇이 구현된 타입은 print 함수에서 사용할 수 있습니다.

def main():
# 사용할 때는 어떤 타입을 쓸지 꺾쇠[]나 인자를 통해 알려줍니다.
print_twice[Int](10) # T가 Int로 변신!
print_twice[String]("Hi") # T가 String으로 변신!
print_twice(3.14) # T가 Float로 변신! (컴파일러가 알아서 추론)
print_twice("Hello") # T가 String으로 변신! (컴파일러가 알아서 추론)
print_twice(True) # T가 Bool로 변신! (컴파일러가 알아서 추론)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build generic.mojo
(project3) chyi@earth:~/Mojo/project3$ ./generic
10 10
Hi Hi
3.14 3.14
Hello Hello
True True
------------------------------------------------------------------------------------------------------------ 🔥 

"아무 타입이나 다 된다(AnyType)"는 것은 사실 굉장히 위험할 수 있다. 예를 들어 두 값을 더하는 add[T: AnyType] (a: T, b: T) 함수를 만들었는데, 사용자가 덧셈을 할 수 없는 이상한 구조체를 T에 집어넣으면 어떻게 될까? 코드가 실행되다가 펑 터질 것이다. 이때 방금 전에 배운 Trait(계약서)가 문지기 역할을 하게된다. AnyType 대신 특정 Trait를 적어주면, "이 계약을 만족하는 타입만 T에 들어올 수 있어!"라고 엄격하게 제한(Constraint)할 수 있는 것이다.

함수뿐만 아니라 구조체(struct)도 제네릭으로 만들 수 있다. 우리가 흔히 쓰는 리스트(List), 딕셔너리(Dict), 포인터(Pointer) 같은 자료구조들이 전부 이 제네릭 구조체로 만들어져 있다고 보면 된다.


[코드 4-8] generics 예제(2) - generic2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
@fieldwise_init
# ExampleStruct 구조체는 제네릭 타입과 매개변수를 사용하여
# 다양한 타입과 값을 처리할 수 있습니다.
struct ExampleStruct:
def example[
T: Writable & Copyable, # type parameter(trait bounds)
count: Int, # value parameter
](
self,
data: String, # argument
init_value: T # generic argument
) -> String:
# Example logic using the generic type and parameters
var result = ""
for _ in range(count):
result += String(data) + " " + String(init_value) + "|"
return result

# main 함수에서는 ExampleStruct의 인스턴스를 생성하고,
# example 메서드를 호출하여 제네릭 타입과 매개변수가 제대로 작동하는지 확인할 수 있습니다.
def main():
var ex = ExampleStruct()
var result = ex.example[Int, 5]("Hello", 42)
print(result)
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build generic2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./generic2
Hello 42|Hello 42|Hello 42|Hello 42|Hello 42|
------------------------------------------------------------------------------------------------------------ 🔥


[코드 4-9] generics 예제(3) - generic3.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# MyCollectionElement라는 새로운 컴파일 타임 타입 제약 조건을 정의합니다.
comptime MyCollectionElement = ImplicitlyCopyable & ImplicitlyDestructible

# T라는 ImplicitlyCopyable & ImplicitlyDestructible 제약 조건을 만족하는
# 어떤 타입이든 들어올 수 있는 함수
def make_filled[T: MyCollectionElement, size: Int](
splat_value: T
) -> List[T]:
var result = List[T](capacity=size) # 컴파일 타임에 크기가 결정되는 리스트 생성
for _ in range(size): # 컴파일 타임 루프를 사용하여 리스트를 채웁니다.
result.append(splat_value)
return result^ # 리스트를 반환할 때 이동하여 반환합니다.

# main 함수에서는 make_filled 함수를 호출하여 제네릭 타입과
# 매개변수가 제대로 작동하는지 확인할 수 있습니다.
def main():
var three_zeros = make_filled[Int, 3](100)
var five_hellos = make_filled[String, 5]("hello")
print(three_zeros) # [100, 100, 100]
print(five_hellos) # [hello, hello, hello, hello, hello]
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build generic3.mojo
(project3) chyi@earth:~/Mojo/project3$ ./generic3
[100, 100, 100]
[hello, hello, hello, hello, hello]
------------------------------------------------------------------------------------------------------------ 🔥


[코드 4-10] generics 예제(4) - generic4.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
# BaseTraits라는 새로운 컴파일 타임 타입 제약 조건을 정의합니다.
comptime BaseTraits = Copyable & ImplicitlyDestructible

@fieldwise_init
struct Wrapper[T: BaseTraits]( # T라는 BaseTraits 제약 조건을 만족하는 어떤 타입
Writable where conforms_to(T, Writable)
# Writable 트레잇을 만족하는 타입에 대해서만 Wrapper가 Writable이 될 수 있도록 제약 조건 추가
):
var value: Self.T

@fieldwise_init
struct NotWritable(BaseTraits):
var data: Int

# main 함수에서는 Wrapper와 NotWritable 구조체의 인스턴스를 생성하여
# 제네릭 타입과 트레잇 제약 조건이 제대로 작동하는지 확인할 수 있습니다.
def main():
var w_int = Wrapper[Int](42) # Int is Writable
print(w_int) # Wrapper[Int](value=42)


var w_str = Wrapper[String]("Hello") # String is Writable
print(w_str) # Wrapper[String](value=Hello)

# 컴파일 에러 발생! NotWritable은 Writable이 아니므로 Wrapper의 T로 사용할 수 없습니다.
#var w_not_writable = Wrapper[NotWritable](NotWritable(10))
_ = Wrapper[NotWritable](NotWritable(10))
# print(w_not_writable) # Compile-time error:
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build generic4.mojo
(project3) chyi@earth:~/Mojo/project3$ ./generic4
Wrapper[Int](value=42)
Wrapper[String](value=Hello)
------------------------------------------------------------------------------------------------------------ 🔥 

파이썬처럼 실행 중(Runtime)에 타입을 대충 검사해서 동작하는 언어들은 느리다. 하지만 Mojo의 제네릭은 컴파일 타임에 작동하는 방식이다. 우리가 Box[Int]와 Box[String]을 사용하면, Mojo 컴파일러는 내부적으로 정수 전용 Box 구조체 코드와 문자열 전용 Box 구조체 코드를 각각 독립적으로 찍어내게 된다. (이를 Monomorphization, 단일화라고 부른다.) 따라서 개발자는 코드를 한 번만 작성해서 편하고(DRY 원칙), 컴퓨터는 실행할 때 자신의 타입에 완벽하게 맞춰진 코드를 바로 실행하므로 C/C++ 수준의 100% 최고 성능을 낼 수 있는 것이다.

4.5 포인터(Pointer)
Mojo에서 포인터는 Python의 사용 편의성과 C/C++ 또는 Rust 수준의 강력한 저수준 메모리 제어를 연결하는 핵심 도구이다. 버전이 올라가면서 포인터 타입들이 메모리 안전성(Safety)과 소유권(Ownership) 시스템에 맞춰 세밀하게 세분화되었다. 현재 Mojo의 포인터는 크게 안전한 포인터(Safe Pointers)와 안전하지 않은 포인터(Unsafe Pointers)로 나눌 수 있다.

1. 안전한 포인터 (Safe Pointers)
안전한 포인터는 Mojo 컴파일러가 수명(Lifetime)과 소유권을 엄격하게 추적하여 메모리 누수나 Dangling Pointer 같은 치명적인 버그를 컴파일 타임에 막아주는 타입들이다.

a) Pointer
  • 자신이 소유하지 않은(doesn't own) 단일 초기화 값을 가리키는 안전한 포인터이다.
  • UnsafePointer와 달리 값과 독립적으로 존재할 수 없으며, 반드시 이미 존재하는 값으로부터 생성되어야 한다(예: 널 포인터 불가).
  • Mojo 컴파일러는 포인터가 생성될 때 원본 값의 출처(Origin)를 기록하여, 포인터가 참조하는 원본 값이 포인터보다 먼저 파괴되지 않도록 수명을 보장한다.
b) OwnedPointer
  • 특정 데이터에 대해 독점적인 소유권(Exclusive Ownership)을 갖는 스마트 포인터이다.
  • 값으로 포인터를 초기화할 때 내부적으로 힙 메모리를 자동으로 할당한다.
📌 C++의 std::unique_ptr과 동일한 개념으로 보면 된다.

c) ArcPointer
  • 하나의 소유된 값을 여러 곳에서 공유해야 할 때 사용하는 참조 카운팅(Reference-counted) 기반의 스마트 포인터이다.
📌 C++의 std::shared_ptr과 동일한 개념으로 보면 된다.

2. 저수준 제어를 위한 포인터 (Unsafe Pointers)
Mojo는 하드웨어와 직접 맞닿는 커널이나 고성능 자료구조를 직접 구현해야 하는 개발자를 위해 제약 없는 포인터도 제공한다.

a) UnsafePointer
  • C나 C++의 원시 포인터(Raw pointer)와 가장 유사하게 동작하는 저수준 포인터이다. 연속된 메모리 블록을 가리키거나 아직 초기화되지 않은 메모리를 참조할 수 있다.
  • 왜 "Unsafe(안전하지 않음)"한가?
    • 수동 메모리 관리: 가비지 컬렉터나 컴파일러가 대신 메모리를 해제해주지 않으므로, 개발자가 직접 메모리를 동적으로 할당(alloc)하고 해제(free)해야 한다.
    • Null 허용 및 미초기화 위험: 포인터가 아무것도 가리키지 않는 Null 상태일 수 있으며, 할당된 메모리 공간에 초기화된 값이 들어있다는 보장이 없다.
    • 안전망 부재: 포인터 연산(Pointer arithmetic)을 수행할 때 컴파일러가 메모리의 경계를 넘어섰는지(Bounds checking) 검사해 주지 않는다.
  • 다만, 최근 버전의 Mojo에서는 UnsafePointer에도 Origin 매개변수가 포함되어 있어 컴파일러가 해당 메모리의 출처를 제한적으로나마 추적할 수 있도록 돕고 있다.
📌 C/C++에서 말하는 일반 pointer(malloc/free or new/delete)를 말한다.


[코드 4-11] Pointer 예제 - pointer.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.memory.pointer import Pointer

struct MyStruct:
var val: Int

def __init__(out self, val: Int):
self.val = val

def main():
# 1. 일반적인 값 생성 (Mojo가 수명을 관리함)
var my_instance = MyStruct(42)

# 2. 존재하는 값으로부터 Pointer 생성 (to= 키워드 사용)
# 안전한 Pointer는 빈 공간(Null)이나 할당되지 않은 메모리를 가리킬 수 없습니다.
var safe_ptr = Pointer(to=my_instance)

# 3. 포인터 역참조(Dereference)를 위해 [] 연산자 사용
print("포인터가 가리키는 값:", safe_ptr[].val) # 출력: 42

# 안전성 보장:
# my_instance 원본 값의 수명은 safe_ptr이 코드에서 사용되는 동안
# Mojo 컴파일러에 의해 자동으로 유효하게 연장됩니다.
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build pointer.mojo
(project3) chyi@earth:~/Mojo/project3$ ./pointer
포인터가 가리키는 값: 42
------------------------------------------------------------------------------------------------------------ 🔥 

[코드 4-12] OwnedPointer 예제 - ownedpointer.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.memory import OwnedPointer

struct MyData(Movable): # Copyable가 아닌 Movable로 정의하여 소유권 이동이 가능하도록 합니다.
var value: Int

def __init__(out self, value: Int):
self.value = value

def main():
# OwnedPointer를 사용해 힙 메모리에 MyData를 할당하고 독점 소유권을 가집니다.
var o_ptr = OwnedPointer(MyData(10))

# [] 역참조 연산자를 통해 내부 데이터에 접근합니다.
print("OwnedPointer 값:", o_ptr[].value)

# o_ptr은 독점적 소유권을 가지므로 아래와 같이 다른 변수에 단순히 복사할 수 없습니다.
# var o_ptr2 = o_ptr # 컴파일 에러 발생!

# 소유권을 다른 곳으로 넘기려면 이동 연산자(^)를 사용해 소유권을 완전히 이전해야 합니다.
var moved_ptr = o_ptr^
print("Moved OwnedPointer 값:", moved_ptr[].value)
# o_ptr은 이미 소유권이 이동되었으므로 더 이상 접근할 수 없습니다.
# print(o_ptr[].value) # 컴파일 에러 발생!
# moved_ptr이 범위를 벗어나면 MyData 인스턴스가 자동으로 파괴되고 메모리가 해제됩니다.
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build ownedpointer.mojo
(project3) chyi@earth:~/Mojo/project3$ ./ownedpointer
OwnedPointer 값: 10
Moved OwnedPointer 값: 10
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 4-13] ArcPointer 예제 - arcpointer.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.memory import ArcPointer

# ArcPointer는 참조 카운팅을 사용하여 메모리를 관리하므로,
# MySharedData는 Copyable로 정의되어야 합니다.
struct MySharedData(Copyable):
var value: Int

def __init__(out self, value: Int):
self.value = value

def main():
# ArcPointer를 초기화하면 힙 메모리가 할당되고 참조 카운트가 1이 됩니다.
var arc_ptr1 = ArcPointer(MySharedData(100))

# ArcPointer는 복사가 가능합니다. 이 변수가 복사되면 참조 카운트가 2로 증가합니다.
var arc_ptr2 = arc_ptr1

# 두 스마트 포인터 모두 동일한 힙 메모리의 데이터를 안전하게 공유하며 참조합니다.
print("ArcPointer 1 값:", arc_ptr1[].value)
print("ArcPointer 2 값:", arc_ptr2[].value)

# 함수가 종료되며 arc_ptr1과 arc_ptr2가 모두 파괴되면 참조 카운트가 0이 되어 메모리가 해제됩니다
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build arcpointer.mojo
(project3) chyi@earth:~/Mojo/project3$ ./arcpointer
ArcPointer 1 값: 100
ArcPointer 2 값: 100
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 4-14] UnsafePointer 예제(1) - unsafepointer.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
#from std.memory import UnsafePointer
from std.memory import alloc

def main():
# 1. 힙 메모리 할당 (Int 타입 데이터 3개가 들어갈 공간)
# Mojo 컴파일러가 관리해주지 않으므로, 개발자가 직접 메모리를 확보(alloc)합니다.
#var ptr = UnsafePointer[Int].alloc(3)
var ptr = alloc[Int](3)

# 2. 포인터 연산 및 메모리 초기화
# 메모리의 경계(Bounds)를 검사하지 않으므로 C++ 수준의 최고 성능을 냅니다.
(ptr + 0)[] = 10 # 포인터 오프셋 덧셈 연산을 통한 접근
ptr[1] = 20 # 배열처럼 대괄호 인덱싱을 통한 접근
ptr[2] = 30

print(ptr[0], ptr[1], ptr[2]) # 출력: 10 20 30

# 3. 메모리 수동 해제 (가장 중요!)
# alloc()으로 할당한 메모리는 자동으로 파괴되지 않습니다.
# 사용이 끝나면 반드시 free()를 호출해야 메모리 누수(Leak)가 발생하지 않습니다.
ptr.free()

# --- 참고: 스택(Stack)의 지역 변수를 UnsafePointer로 가리키기 ---
var number = 100
var ptr_to_number = UnsafePointer(to=number)
print(ptr_to_number[]) # 출력: 100
# 이 경우 힙 메모리를 직접 할당(alloc)한 것이 아니므로 .free()를 호출하면 안 됩니다!
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build unsafepointer.mojo
(project3) chyi@earth:~/Mojo/project3$ ./unsafepointer
10 20 30
100
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 4-15] UnsafePointer 예제(2) - unsafepointer2.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
#from std.memory import alloc, UnsafePointer
from std.memory import alloc

def main():
# 1. Allocate space for 5 Int
var size = 5
var ptr = alloc[Int](size)
# 2. Initialize the memory
for i in range(size):
(ptr + i).init_pointee_copy(i * 100)

# Use the value
for i in range(size):
print(ptr[i])

# 3 & 4. Cleanup
ptr.destroy_pointee()
ptr.free()
------------------------------------------------------------------------------------------------------------ 🔥 

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build unsafepointer2.mojo
(project3) chyi@earth:~/Mojo/project3$ ./unsafepointer2
0
100
200
300
400
------------------------------------------------------------------------------------------------------------ 🔥 

Mojo에서 포인터 구조체(Pointer, UnsafePointer 등)를 다루는 것과 함수의 매개변수 등에서 사용하는 참조 시스템(Reference System)은 구별해서 이해해야 한다. Mojo 언어 내부적으로는 변수를 단순히 다른 이름으로 참조하기 위해 ref, read, mut 같은 키워드를 통해 안전하게 원본 데이터를 차용(Borrow)하는 강력한 기능을 제공한다. 따라서 명시적인 동적 메모리 할당이나 특정 메모리 주소를 직접 제어해야 하는 상황이 아니라면, 포인터 객체를 직접 생성하는 것보다 언어 자체의 참조 키워드와 소유권 이전 연산자(^)를 활용하는 것이 훨씬 안전하고 권장되는 방식이다.


5. GPU Programming
이제부터는 mojo를 이용하여 GPU(Nvidia, AMD, Apple M4 등) programming을 하는 방법을 소개하고자 한다.

[그림 5-1] CPU와 GPU의 관계 [출처 - 참고문헌 2]

Mojo를 배우려는 많은 개발자들이 가장 기대하는 하이라이트가 바로 GPU 프로그래밍다. 기존 Python 환경(PyTorch, TensorFlow 등)에서는 최고 성능을 내거나 커스텀 GPU 연산을 구현하려면 어쩔 수 없이 무거운 C++ 코드와 CUDA(NVIDIA 전용 언어)를 섞어 써야 했다.

하지만 Mojo는 "CUDA 없이 Mojo 언어 하나만으로 GPU 커널을 직접 작성하고 최고 성능을 낸다"는 강력한 철학을 가지고 있다. 최신 공식 문서에서도 이를 하드웨어 독립적(Hardware-agnostic) GPU 프로그래밍이라고 강조하고 있다.

1. 하드웨어 독립적 gpu 패키지
과거에는 NVIDIA GPU면 CUDA, AMD GPU면 ROCm 등 하드웨어마다 다른 언어와 라이브러리를 써야 했다. 하지만 Mojo는 표준 라이브러리인 gpu 패키지를 제공하여, 개발자가 하나의 Mojo 코드를 작성하면 컴파일러가 알아서 현재 꽂혀있는 하드웨어(CPU, GPU, NPU)에 맞는 최적의 기계어로 번역해 준다.

2. GPU 커널(Kernel) 직접 작성하기
GPU는 수천 개의 아주 작은 코어(스레드)가 동시에 같은 일을 처리하는 데 특화되어 있다. Mojo에서는 parallelize나 vectorize를 넘어, GPU 스레드 하나하나가 무슨 일을 할지 명시하는 커널(Kernel) 함수를 직접 구현한다.

3. GPU 메모리 계층 제어(Shared Memory 등)
CUDA C++를 써보았다면 느끼는 거지만, GPU의 성능을 영혼까지 끌어모으려면 속도가 매우 빠른 공유 메모리(Shared Memory)를 잘 써야 한다. Mojo는 단순히 고수준의 텐서 연산만 지원하는 것이 아니라, C++처럼 GPU 내부의 L1/L2 캐시나 공유 메모리를 직접 할당하고 스레드 간의 동기화(Barrier)를 제어하는 저수준 API도 모두 제공한다.

4. CPU SIMD와의 통합
Mojo의 또 다른 장점은 CPU용 고성능 벡터 연산 타입인 SIMD를 GPU 커널 안에서도 거의 똑같은 감각으로 쓸 수 있다는 점이다. 데이터 타입을 엄격하게 맞추고, 앞서 배운 메타프로그래밍(컴파일 타임 제네릭 [ ])을 활용하면, 런타임 오버헤드 0%로 무시무시하게 빠른 GPU 코드가 탄생하게 된다.

[코드 5-1] GPU 사용 예제(1) - detect_gpu.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.sys import has_accelerator

# GPU 가속기 존재 여부를 확인하는 간단한 예제입니다.
def main():
comptime if has_accelerator(): # GPU 가속기가 감지되면
print("GPU detected")
# Enable GPU processing
else:
print("No GPU detected")
# Print error or fall back to CPU-only execution
------------------------------------------------------------------------------------------------------------ 🔥

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build detect_gpu.mojo
(project3) chyi@earth:~/Mojo/project3$ ./detect_gpu
GPU detected
------------------------------------------------------------------------------------------------------------ 🔥 


[코드 5-2GPU 사용 예제(2) - scalar_add.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.math import iota
from std.sys import exit, has_accelerator

from std.gpu.host import DeviceContext
from std.gpu import block_dim, block_idx, thread_idx

comptime num_elements = 20

# UnsafePointer는 Mojo에서 C++ 스타일의 포인터 연산을 가능하게 하는 타입입니다.
# UnsafePointer는 Mojo의 안전한 메모리 모델을 우회하여 직접 메모리 주소를 다룰 수 있게 해줍니다.
# 이 예제에서는 GPU에서 벡터에 스칼라 값을 더하는 커널 함수를 구현하기 위해 UnsafePointer를 사용합니다.
def scalar_add(
vector: UnsafePointer[Float32, MutAnyOrigin],
size: Int,
scalar: Float32,
):
"""
Kernel function to add a scalar to all elements of a vector.

This kernel function adds a scalar value to each element of a vector stored
in GPU memory. The input vector is modified in place.

Args:
vector: Pointer to the input vector.
size: Number of elements in the vector.
scalar: Scalar to add to the vector.

"""

# 전체 그리드 내에서 전역 스레드 인덱스를 계산합니다. 각 스레드는 벡터의 한 요소를 처리합니다.
# block_idx.x: index of the current thread block.
# block_dim.x: number of threads per block.
# thread_idx.x: index of the current thread within its block.
idx = block_idx.x * block_dim.x + thread_idx.x

# 경계 검사: 벡터 크기를 초과하는 메모리 영역에 접근하지 않도록 합니다.
# 이는 스레드 수가 벡터 크기와 정확히 일치하지 않을 때 특히 중요합니다.
if idx < size:
# 각 스레드는 해당 벡터 요소에 스칼라 값을 더합니다.
# 이 연산은 모든 GPU 스레드에서 병렬로 수행됩니다.
vector[idx] += scalar


def main() raises:
comptime if not has_accelerator():
print("No GPUs detected")
exit(0)
else:
# DeviceContext는 GPU와의 상호작용을 관리하는 객체입니다.
# GPU에서 커널 함수를 실행하려면 먼저 컨텍스트를 생성해야 합니다.
ctx = DeviceContext()

# 호스트(CPU) 메모리에 입력 데이터를 저장할 버퍼를 생성합니다.
host_buffer = ctx.enqueue_create_host_buffer[DType.float32](
num_elements
)

# GPU 작업은 비동기적으로 실행되므로, synchronize()를 호출하여
# CPU가 GPU 작업이 끝날 때까지 대기하도록 합니다.
ctx.synchronize()

# iota() 함수는 주어진 범위에 연속된 값을 채우는 유틸리티 함수입니다.
# 여기서는 0부터 시작하여 size-1 까지의 정수를 host_buffer에 채웁니다.
iota(host_buffer.as_span())
print("Original host buffer:", host_buffer)

# Create a buffer in device (GPU) memory to store data for computation.
# enqueue_create_buffer는 GPU 메모리에 버퍼를 할당하는 함수입니다.
# 이 버퍼는 GPU에서 커널 함수가 읽고 쓸 수 있는 공간입니다.
device_buffer = ctx.enqueue_create_buffer[DType.float32](num_elements)

# Copy data from host memory to device memory for GPU processing.
# enqueue_copy는 호스트와 디바이스 간에 데이터를 전송하는 함수입니다.
ctx.enqueue_copy(src_buf=host_buffer, dst_buf=device_buffer)

# Compile the scalar_add kernel function for execution on the GPU.
# compile_function은 GPU에서 실행할 함수를 컴파일하는 함수입니다.
scalar_add_kernel = ctx.compile_function[
scalar_add, scalar_add
]()

# 다음 인수를 사용하여 GPU 커널을 실행하십시오:
#
# - device_buffer: GPU memory containing our vector data
# - num_elements: number of elements in the vector
# - Float32(20.0): the scalar value to add to each element
# - grid_dim=1: use 1 thread block
# - block_dim=num_elements: use 'num_elements' threads per block (one
# thread per vector element)
# enqueue_function은 GPU에서 커널 함수를 실행하는 함수입니다. grid_dim과 block_dim을 설정하여
# GPU에서 실행되는 스레드의 수와 구조를 정의합니다.
ctx.enqueue_function(
scalar_add_kernel,
device_buffer,
num_elements,
Float32(20.0),
grid_dim=1,
block_dim=num_elements,
)

# Copy the computed results back from device memory to host memory.
# enqueue_copy는 호스트와 디바이스 간에 데이터를 전송하는 함수입니다.
ctx.enqueue_copy(src_buf=device_buffer, dst_buf=host_buffer)

# GPU 작업은 비동기적으로 실행되므로, synchronize()를 호출하여
# CPU가 GPU 작업이 끝날 때까지 대기하도록 합니다.
ctx.synchronize()

# Display the final results after GPU computation.
print("Modified host buffer:", host_buffer)
------------------------------------------------------------------------------------------------------------ 🔥

<How to build & run>
(project3) chyi@earth:~/Mojo/project3$ mojo build scalar_add.mojo
(project3) chyi@earth:~/Mojo/project3$ ./scalar_add
Original host buffer: HostBuffer([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0])
Modified host buffer: HostBuffer([20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0])
------------------------------------------------------------------------------------------------------------ 🔥 

[코드 5-3] GPU 사용 예제(3) - print_threads.mojo
🔥 ------------------------------------------------------------------------------------------------------------ 
from std.sys import has_accelerator

from std.gpu.host import DeviceContext
from std.gpu import block_dim, block_idx, global_idx, thread_idx

# 이 함수는 GPU에서 실행되는 커널 함수입니다.
# 각 GPU 스레드는 이 함수를 실행하여 자신의 블록 및 스레드 인덱스를 출력합니다.
def print_threads():
"""Print thread block and thread indices."""

print(
block_idx.x,
block_idx.y,
block_idx.z,
thread_idx.x,
thread_idx.y,
thread_idx.z,
global_idx.x,
global_idx.y,
global_idx.z,
block_dim.x * block_idx.x + thread_idx.x,
block_dim.y * block_idx.y + thread_idx.y,
block_dim.z * block_idx.z + thread_idx.z,
sep="\t",
)

# GPU에서 실행되는 커널 함수는 일반적으로 void 반환형이지만,
# Mojo에서는 반환형을 명시하지 않아도 됩니다.
def main() raises:
comptime if not has_accelerator():
print("No compatible GPU found")
else:
# DeviceContext는 GPU와의 상호작용을 관리하는 객체입니다.
# GPU에서 커널 함수를 실행하려면 먼저 컨텍스트를 생성해야 합니다.
ctx = DeviceContext()

print("block_idx\t\tthread_idx\t\tglobal_idx\t\tcalculated global_idx")
print("x\ty\tz", "x\ty\tz", "x\ty\tz", "x\ty\tz", sep="\t")
print("-" * 20, "-" * 20, "-" * 20, "-" * 20, sep="\t")

# GPU에서 print_threads 커널 함수를 실행합니다. grid_dim과 block_dim을 설정하여
# GPU에서 실행되는 스레드의 수와 구조를 정의합니다.
ctx.enqueue_function[print_threads, print_threads](
grid_dim=(2, 2, 1), # 2x2x1 blocks per grid
block_dim=(4, 4, 2), # 4x4x2 threads per block
)

# GPU에서 커널 실행이 완료될 때까지 기다립니다. GPU 작업은 비동기적으로 실행되므로,
# synchronize()를 호출하여 CPU가 GPU 작업이 끝날 때까지 대기하도록 합니다.
ctx.synchronize()
print("Done")
------------------------------------------------------------------------------------------------------------ 🔥

(project3) chyi@earth:~/Mojo/project3$ mojo build print_threads.mojo
(project3) chyi@earth:~/Mojo/project3$ ./print_threads
block_idx               thread_idx              global_idx              calculated global_idx
x       y       z       x       y       z       x       y       z       x       y       z
--------------------    --------------------    --------------------    --------------------
0       0       0       0       0       0       0       0       0       0       0       0
0       0       0       1       0       0       1       0       0       1       0       0
0       0       0       2       0       0       2       0       0       2       0       0
0       0       0       3       0       0       3       0       0       3       0       0
0       0       0       0       1       0       0       1       0       0       1       0
0       0       0       1       1       0       1       1       0       1       1       0
0       0       0       2       1       0       2       1       0       2       1       0
0       0       0       3       1       0       3       1       0       3       1       0
0       0       0       0       2       0       0       2       0       0       2       0
0       0       0       1       2       0       1       2       0       1       2       0
0       0       0       2       2       0       2       2       0       2       2       0
0       0       0       3       2       0       3       2       0       3       2       0
0       0       0       0       3       0       0       3       0       0       3       0
0       0       0       1       3       0       1       3       0       1       3       0
0       0       0       2       3       0       2       3       0       2       3       0
0       0       0       3       3       0       3       3       0       3       3       0
0       0       0       0       0       1       0       0       1       0       0       1
0       0       0       1       0       1       1       0       1       1       0       1
0       0       0       2       0       1       2       0       1       2       0       1
0       0       0       3       0       1       3       0       1       3       0       1
0       0       0       0       1       1       0       1       1       0       1       1
0       0       0       1       1       1       1       1       1       1       1       1
0       0       0       2       1       1       2       1       1       2       1       1
0       0       0       3       1       1       3       1       1       3       1       1
0       0       0       0       2       1       0       2       1       0       2       1
0       0       0       1       2       1       1       2       1       1       2       1
0       0       0       2       2       1       2       2       1       2       2       1
0       0       0       3       2       1       3       2       1       3       2       1
0       0       0       0       3       1       0       3       1       0       3       1
0       0       0       1       3       1       1       3       1       1       3       1
0       0       0       2       3       1       2       3       1       2       3       1
0       0       0       3       3       1       3       3       1       3       3       1
0       1       0       0       0       0       0       4       0       0       4       0
0       1       0       1       0       0       1       4       0       1       4       0
0       1       0       2       0       0       2       4       0       2       4       0
0       1       0       3       0       0       3       4       0       3       4       0
0       1       0       0       1       0       0       5       0       0       5       0
0       1       0       1       1       0       1       5       0       1       5       0
0       1       0       2       1       0       2       5       0       2       5       0
0       1       0       3       1       0       3       5       0       3       5       0
0       1       0       0       2       0       0       6       0       0       6       0
0       1       0       1       2       0       1       6       0       1       6       0
0       1       0       2       2       0       2       6       0       2       6       0
0       1       0       3       2       0       3       6       0       3       6       0
0       1       0       0       3       0       0       7       0       0       7       0
0       1       0       1       3       0       1       7       0       1       7       0
0       1       0       2       3       0       2       7       0       2       7       0
0       1       0       3       3       0       3       7       0       3       7       0
0       1       0       0       0       1       0       4       1       0       4       1
0       1       0       1       0       1       1       4       1       1       4       1
0       1       0       2       0       1       2       4       1       2       4       1
0       1       0       3       0       1       3       4       1       3       4       1
0       1       0       0       1       1       0       5       1       0       5       1
0       1       0       1       1       1       1       5       1       1       5       1
0       1       0       2       1       1       2       5       1       2       5       1
0       1       0       3       1       1       3       5       1       3       5       1
0       1       0       0       2       1       0       6       1       0       6       1
0       1       0       1       2       1       1       6       1       1       6       1
0       1       0       2       2       1       2       6       1       2       6       1
0       1       0       3       2       1       3       6       1       3       6       1
0       1       0       0       3       1       0       7       1       0       7       1
0       1       0       1       3       1       1       7       1       1       7       1
0       1       0       2       3       1       2       7       1       2       7       1
0       1       0       3       3       1       3       7       1       3       7       1
1       0       0       0       0       0       4       0       0       4       0       0
1       0       0       1       0       0       5       0       0       5       0       0
1       0       0       2       0       0       6       0       0       6       0       0
1       0       0       3       0       0       7       0       0       7       0       0
1       0       0       0       1       0       4       1       0       4       1       0
1       0       0       1       1       0       5       1       0       5       1       0
1       0       0       2       1       0       6       1       0       6       1       0
1       0       0       3       1       0       7       1       0       7       1       0
1       0       0       0       2       0       4       2       0       4       2       0
1       0       0       1       2       0       5       2       0       5       2       0
1       0       0       2       2       0       6       2       0       6       2       0
1       0       0       3       2       0       7       2       0       7       2       0
1       0       0       0       3       0       4       3       0       4       3       0
1       0       0       1       3       0       5       3       0       5       3       0
1       0       0       2       3       0       6       3       0       6       3       0
1       0       0       3       3       0       7       3       0       7       3       0
1       0       0       0       0       1       4       0       1       4       0       1
1       0       0       1       0       1       5       0       1       5       0       1
1       0       0       2       0       1       6       0       1       6       0       1
1       0       0       3       0       1       7       0       1       7       0       1
1       0       0       0       1       1       4       1       1       4       1       1
1       0       0       1       1       1       5       1       1       5       1       1
1       0       0       2       1       1       6       1       1       6       1       1
1       0       0       3       1       1       7       1       1       7       1       1
1       0       0       0       2       1       4       2       1       4       2       1
1       0       0       1       2       1       5       2       1       5       2       1
1       0       0       2       2       1       6       2       1       6       2       1
1       0       0       3       2       1       7       2       1       7       2       1
1       0       0       0       3       1       4       3       1       4       3       1
1       0       0       1       3       1       5       3       1       5       3       1
1       0       0       2       3       1       6       3       1       6       3       1
1       0       0       3       3       1       7       3       1       7       3       1
1       1       0       0       0       0       4       4       0       4       4       0
1       1       0       1       0       0       5       4       0       5       4       0
1       1       0       2       0       0       6       4       0       6       4       0
1       1       0       3       0       0       7       4       0       7       4       0
1       1       0       0       1       0       4       5       0       4       5       0
1       1       0       1       1       0       5       5       0       5       5       0
1       1       0       2       1       0       6       5       0       6       5       0
1       1       0       3       1       0       7       5       0       7       5       0
1       1       0       0       2       0       4       6       0       4       6       0
1       1       0       1       2       0       5       6       0       5       6       0
1       1       0       2       2       0       6       6       0       6       6       0
1       1       0       3       2       0       7       6       0       7       6       0
1       1       0       0       3       0       4       7       0       4       7       0
1       1       0       1       3       0       5       7       0       5       7       0
1       1       0       2       3       0       6       7       0       6       7       0
1       1       0       3       3       0       7       7       0       7       7       0
1       1       0       0       0       1       4       4       1       4       4       1
1       1       0       1       0       1       5       4       1       5       4       1
1       1       0       2       0       1       6       4       1       6       4       1
1       1       0       3       0       1       7       4       1       7       4       1
1       1       0       0       1       1       4       5       1       4       5       1
1       1       0       1       1       1       5       5       1       5       5       1
1       1       0       2       1       1       6       5       1       6       5       1
1       1       0       3       1       1       7       5       1       7       5       1
1       1       0       0       2       1       4       6       1       4       6       1
1       1       0       1       2       1       5       6       1       5       6       1
1       1       0       2       2       1       6       6       1       6       6       1
1       1       0       3       2       1       7       6       1       7       6       1
1       1       0       0       3       1       4       7       1       4       7       1
1       1       0       1       3       1       5       7       1       5       7       1
1       1       0       2       3       1       6       7       1       6       7       1
1       1       0       3       3       1       7       7       1       7       7       1
Done
------------------------------------------------------------------------------------------------------------ 🔥 

지금까지 Mojo manual을 참조하여, 가장 기초적인 Mojo 문법을 정리해 보았다. 이번 posting에서 사용한 예제 코드는 아래 link에서 확인 가능하다.


(사실 필자도 아직은 mojo를 완벽하게 이해한 것이 아닌만큼) 내용 중, 당연히 아쉬운 부분이 많이 보인다. 부족한 부분은 독자 여러분의 몫으로 남긴다. 😂

(이미 언급한 바와 같이)Mojo는 아직 공식으로 1.0 version이 release되지 않은 상태이다. 그 말은 아직도 열심히 개발 중인 프로젝트인 관계로, 계속해서 문법이 변경되고 있다는 뜻이기도 하다. 따라서 이번 posting에서 소개한 내용(혹은 내용 중 일부)이 얼마가지 않아 의미없는 내용이 될 수도 있다. 그럼에도 불구하고, 지금까지 설명한 내용을 한번 훑어본 뒤, 공식 매뉴얼을 들여다 본다면, Mojo를 조금이나마 빨리 이해하는데 도움이 되지 않을까 싶다. 😎

To be continued...


References
[1] https://www.modular.com/open-source/mojo
[2] https://docs.modular.com/stable/mojo/manual/
[3] https://ruhati.net/mojo/
[4] https://github.com/modular/skills/blob/main/mojo-syntax/SKILL.md
[5] https://mojo-lang.com/miji/
[6] https://www.modular.com/blog/mojo-llvm-2023
[7] https://llvm.org/devmtg/2020-09/slides/MLIR_Tutorial.pdf
[8] https://puzzles.modular.com/introduction.html
[9] https://docs.modular.com/max/develop/custom-kernels-pytorch/
[10] https://docs.modular.com/max/intro/
[11] https://www.modular.com/open-source/max

[12] https://news.hada.io/topic?id=22947
[13] https://docs.nvidia.com/cuda/cuda-c-programming-guide/
[14] https://pixi.prefix.dev/latest/#getting-started

[15] Google and Gemini 3 pro~


Slowboot