Menu

Zero Hello World: 첫 .0 프로그램 작성하고 실행하기

Zero로 작성하는 첫 번째 프로그램. 정석 hello-world 예제의 모든 토큰이 무엇을 뜻하는지, zero run으로 어떻게 실행하는지, 왜 다섯 줄짜리 프로그램에서도 이미 World, raises, check가 등장하는지 살펴봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 — 편집하고 실행하면 결과를 바로 볼 수 있습니다.

파일 작성하기

에디터를 열고 다음 다섯 줄을 편한 위치에 hello.0로 저장하거나, 아래 블록의 Run 버튼을 눌러 보세요.

이걸로 완성된 Zero 프로그램입니다. 로컬에서 실행하려면:

zero run hello.0

다음과 같이 출력됩니다.

hello from zero

command not found가 뜬다면 Zero 설치 문서로 돌아가 zero --version이 먼저 동작하는지 확인하세요.

모든 토큰 뜯어보기

이 작은 프로그램만으로도 Zero를 다른 언어와 구분 짓는 요소가 거의 다 등장합니다. 왼쪽부터 차례대로 읽어 봅시다.

pub

pub은 선언을 공개로 표시합니다 — 현재 모듈 바깥에서도 보이게 만들죠. 런타임은 파일 바깥의 범위에서 main을 찾아야 하기 때문에 mainpub이어야 합니다. 같은 파일 안에서만 쓰이는 비공개 도우미 함수라면 pub이 필요 없습니다.

fun

fun은 함수 선언을 시작하는 키워드입니다. Zero는 fn(Rust)도, func(Go)도, function(JavaScript)도 아닌 fun을 씁니다. 키워드 하나를 늘 같은 방식으로 사용합니다.

main

관습적인 진입점 이름입니다. Zero 실행 타겟이 시작되면 런타임은 pub fun main(world: World)를 찾아 호출합니다. 다른 함수들은 자유롭게 이름을 지어도 되지만, main은 프로그램 진입점으로 관습상 예약되어 있습니다.

(world: World)

유일한 매개변수의 이름은 world이고 타입은 World입니다. 런타임은 main을 호출하기 전에 World 값을 만들어 인자로 전달합니다. 이 값은 프로그램의 _능력_을 담고 있습니다. 표준 출력, 표준 입력, 파일 시스템, 네트워크 등에 대한 접근 권한 말이죠. 런타임이 어떤 권한을 부여하느냐에 따라 달라집니다.

매개변수 이름은 원하는 대로 골라도 됩니다. (w: World)(io: World)로 써도 잘 컴파일됩니다. 예제의 관례는 world이고, 우리도 그 관례를 따르겠습니다.

-> Void

반환 타입입니다. Void는 함수가 의미 있는 값을 반환하지 않는다는 뜻입니다. 값이 아니라 부수 효과를 위해 존재하는 함수죠. 직접 작성하는 함수 중에는 i32나 직접 정의한 shape 같은 실제 타입을 반환하는 경우도 많습니다.

raises

특정 에러 타입 없이 붙은 raises는 "이 함수가 실패할 수 있다"는 뜻입니다. main의 경우에는, 본문 안에서 check가 에러를 위로 전파하면 프로그램이 0이 아닌 상태로 종료될 수 있다는 의미입니다. raises { InvalidInput }처럼 좁은 형태는 Raises와 Check에서 살펴봅니다.

check world.out.write("hello from zero\n")

실제로 무언가를 하는 줄입니다. 세 부분으로 나뉩니다.

  • world.out — 표준 출력 스트림. World 능력의 필드로 노출됩니다.
  • .write("hello from zero\n") — 그 스트림에 문자열을 쓰는 메서드. 실패할 수 있는 결과를 반환합니다(쓰기가 실패할 수 있고, 스트림이 닫혀 있을 수도 있죠).
  • check — 실패가 일어났을 때 그 실패를 위로 전파합니다. check가 없으면 컴파일러는 write의 결과가 조용히 버려진다고 불평합니다.

끝의 \n은 리터럴 줄바꿈입니다. 이게 없으면 출력이 새 줄로 넘어가지 않아서, 셸 프롬프트가 메시지 같은 줄에 붙어 버립니다.

방금 일어난 일

zero run hello.0을 실행하면 이런 일이 일어납니다.

  1. 컴파일러가 파일을 파싱하고 타입 검사를 합니다.
  2. 사용자 플랫폼에 맞는 작은 네이티브 실행 파일을 만듭니다.
  3. 런타임이 현재 프로세스에 대한 World 능력을 구성합니다.
  4. main(world)를 호출합니다.
  5. 작성한 코드가 그 world의 out 스트림에 "hello from zero\n"을 씁니다. 이 스트림은 터미널의 표준 출력에 연결되어 있습니다.
  6. mainVoid를 반환하고, 런타임이 정리한 뒤, 프로그램은 상태 0으로 종료됩니다.

가비지 컬렉터도, 숨은 런타임 초기화도, 암묵적인 모듈 부트스트랩도 없습니다. 프로그램 전체는 작성한 함수와 그것이 호출하는 표준 라이브러리 코드가 전부입니다.

작은 변경 시도해 보기

메시지를 바인딩으로 받은 뒤에 쓰도록 바꿔 봅시다.

다시 실행하면 출력은 같지만, 이제 let 바인딩을 사용해 봤고, 문자열이 여기저기 전달할 수 있는 1급 값이라는 것도 확인한 셈입니다.

실패하는 변형

check를 빠뜨리면 어떻게 될까요?

pub fun main(world: World) -> Void raises {
    world.out.write("oops\n")
}

zero check hello.0은 이 코드의 컴파일을 거부합니다. write의 결과는 실패할 수 있는 값이고, 그걸 무시하는 것은 컴파일 에러입니다. check로 에러를 전파하든, 명시적으로 처리하든 둘 중 하나여야 합니다. Rust의 must_use 결과와 같은 아이디어인데, Zero에서는 모든 실패 가능한 호출에 대해 강제됩니다.

다음 글: Zero CLI

여기서는 zero run을 썼습니다. CLI에는 알아둘 가치가 있는 작은 명령 모음이 있습니다 — check, run, build, test, fix, explain — 각각 에이전트가 소비할 수 있도록 구조화된 --json 모드를 지원합니다.

자주 묻는 질문

Zero에서 hello world 프로그램은 어떻게 생겼나요?

Zero의 정석 hello world는 다섯 줄입니다: pub fun main(world: World) -> Void raises { check world.out.write("hello from zero\n") }. hello.0로 저장한 뒤 zero run hello.0으로 실행하면 됩니다.

Zero에서 pub fun main은 무슨 뜻인가요?

pub은 선언을 공개로 만들어 — 모듈 바깥에서도 보이게 합니다. fun은 함수를 선언하는 키워드이고, main은 실행 가능한 타겟에서 Zero가 찾는 관습적인 진입점이에요. 합치면 pub fun main은 런타임이 프로그램 시작 시 호출하는 공개 진입점을 선언하는 셈입니다.

왜 main이 World 매개변수를 받나요?

Zero에는 전역 I/O가 없습니다. 바깥 세상과 닿는 모든 것 — 표준 출력, 표준 입력, 파일, 네트워크 — 은 명시적으로 전달되는 능력을 통해 처리됩니다. 런타임은 mainWorld 값을 건네고, 함수가 I/O를 하려면 그 값(또는 그 일부)을 거쳐야 합니다. 덕분에 부수 효과가 함수 시그니처에 드러나게 됩니다.

hello world에서 raisescheck는 무슨 역할을 하나요?

main에 붙은 raises는 이 함수가 실패할 수 있음을 선언합니다. check world.out.write(...)는 실패할 수 있는 함수를 호출하면서, 실패하면 그 에러를 호출자(여기서는 런타임이며, 0이 아닌 상태로 종료하게 됩니다)에 전파합니다. check가 없다면 컴파일러는 에러가 처리되지 않았다는 이유로 컴파일을 거부합니다.

Zero는 어떤 파일 확장자를 쓰나요?

Zero 소스 파일은 .0 확장자를 사용합니다(알파벳 O가 아니라 숫자 0입니다). hello.0는 Zero 소스 파일입니다. 컴파일러는 zero check hello.0, zero run hello.0 같은 명령으로 호출합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기