Tips for Migrating to Luma

Tips for Migrating to Luma

If you’re coming from another language, here are the key differences to keep in mind when writing Luma.

Coming from Python

Python Luma
x = 5 x: int = 5
def greet(name): fn greet(name: str) -> str {
for x in range(5): (0..5).walk(x) -> {
for i, v in enumerate(items): items.walk(i, v) -> {
continue skip
break stop
print(f"Hello {name}") print("Hello ${name}")
if x: (truthy) if x != 0 { (explicit)
None nil
Optional[int] ?int
dict {str: int}
set {int}
list [int]

Key differences:

  • Luma requires type annotations on declarations
  • No implicit truthiness: if count: must be if count > 0 {
  • Braces {} instead of indentation for blocks
  • No class keyword: use struct + impl + trait
  • String interpolation uses ${} instead of f"{}"

Coming from JavaScript/TypeScript

JS/TS Luma
let x = 5 x: int = 5
const x = 5 lock x: int = 5
function greet(name) { fn greet(name: str) -> str {
for (let i = 0; i < 5; i++) (0..5).walk(i) -> {
array.forEach(x => ...) list.walk(x) -> ...
continue skip
break stop
array.filter(x => x > 0) list.filter(x -> x > 0)
`Hello ${name}` "Hello ${name}"
null / undefined nil
interface trait

Key differences:

  • Luma uses -> for lambdas, not =>
  • => is used for expression-body functions: fn double(x: int) -> int => x * 2
  • No this keyword: methods use self
  • No var / let / const distinction: just name: type or lock name: type
  • No semicolons needed

Coming from Go

Go Luma
x := 5 x: int = 5
func greet(name string) string { fn greet(name: str) -> str {
for i := 0; i < 5; i++ { (0..5).walk(i) -> {
for _, v := range items { items.walk(v) -> {
continue skip
break stop
fmt.Println(x) print(x)
map[string]int {str: int}
[]int [int]
*int (nil pointer) ?int (optional)
json.Marshal(v) json.encode(v)
json.Unmarshal(data, &v) json.decode(data)

Key differences:

  • Luma types use shorter names: str not string, float not float64
  • No pointers: optionals (?T) replace nil pointer patterns
  • No goroutines or channels (concurrency is planned)
  • Iteration uses .walk() instead of for range
  • No explicit error returns: Luma uses optionals and panics for now
  • String interpolation built in: "${name}" instead of fmt.Sprintf

Coming from Rust

Rust Luma
let x: i32 = 5; x: int = 5
fn greet(name: &str) -> String { fn greet(name: str) -> str {
for i in 0..5 { (0..5).walk(i) -> {
for i in 0..=5 { (0..=5).walk(i) -> {
continue skip
break stop
Option<i32> ?int
None nil
impl Trait for Type impl Trait for Type {
println!("{}", x) print(x)

Key differences:

  • No ownership, borrowing, or lifetimes
  • No match (pattern matching is planned)
  • Ranges use the same .. and ..= syntax but require .walk() for iteration
  • Much simpler type system: no generics yet, no lifetime annotations
  • Luma is designed for clarity over performance guarantees

General tips

  1. Always annotate types on variable declarations. Luma uses type inference in expressions but declarations need explicit types.

  2. Use ?T for nullable values. Regular types cannot be nil. If a value might be absent, declare it as optional.

  3. Iterate with .walk(), not traditional for loops. This is Luma’s universal iteration pattern for lists, sets, maps, ranges, and files. Use skip instead of continue and stop instead of break.

  4. String interpolation uses ${} inside double-quoted strings. No special prefix needed.

  5. No semicolons. Statements are terminated by newlines.

  6. Immutability uses the lock keyword: lock pi: float = 3.14159

Last updated on