Zig Tutorial - 8일차
포스트
취소

Zig Tutorial - 8일차

Zig 언어 공부를 시작합니다. 설치부터 문법 및 간단한 응용까지 다룹니다.

0. 튜토리얼 참고문서

1. 커맨드라인 Args 출력하기

표준 입력의 args 를 출력하는 간단한 코드이다.

참고 : 피보나치 샘플 소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// wasi_args.zig

const std = @import("std");

pub fn main() !void {
    // 메모리 할당자
    const alloc: std.mem.Allocator = std.heap.page_allocator;

    // args 처리에 alloc 연결
    const args = try std.process.argsAlloc(alloc);
    defer alloc.free(args);

    // 표준 출력 핸들러
    const stdout = std.io.getStdOut();
    defer stdout.close();

    // writer 별칭 선언
    const out = stdout.writer();

    try out.print("Print args example: len={}\n", .{args.len});

    // 입력된 args 를 모두 출력
    for (args, 0..) |arg, i| {
        try out.print("{}: {s}\n", .{ i, arg });
    }
}

원래 참고하려던 WASI 예제

소스 레벨에서 오류가 나서 빌드 실패! 그래서 다른 샘플 코드를 뜯어와 만든거다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 오류: 작동안됨

const std = @import("std");

pub fn main() !void {
    var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
    const gpa = general_purpose_allocator.allocator();
    const args = try std.process.argsAlloc(gpa);
    defer std.process.argsFree(gpa, args);

    for (args, 0..) |arg, i| {
        std.debug.print("{}: {s}\n", .{ i, arg });
    }
}

빌드하면 std/heap/general_purpose_allocator.zig 에서 오류가 뜬다.

1
2
3
$ zig build-exe src/wasi_args_old.zig -target wasm32-wasi
src/wasi_args_old.zig:4:77: error: expected type 'heap.general_purpose_allocator.GeneralPurposeAllocator(.{ .stack_trace_frames = 0, .enable_memory_limit = false, .safety = true, .thread_safe = false, .MutexType = null, .never_unmap = false, .retain_metadata = false, .verbose_log = false })', found '@TypeOf(.enum_literal)'
    var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;

실행 및 빌드

zig run 명령에 args 를 추가하려면 -- 다음에 붙이면 된다. (출처)

1
2
3
4
5
$ zig run src/wasi_args.zig -- 123 hello
Print args example: len=3
0: /Users/bgmin/.cache/zig/o/fcdbc87fdd24476dce088641df04e9d8/wasi_args
1: 123
2: hello

빌드하여 실행하기

zig build-exe 명령으로 실행 가능한 binary 를 생성하여 실행한다.

  • 빌드시 -O 최적화 옵션을 사용하면 빠르게 또는 작게 만들기 등을 할 수 있다.
  • -O ReleaseSmall 사용시 1/10 수준으로 크기가 작아진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ zig build-exe src/wasi_args.zig

$ ./wasi_args 123 hello
Print args example: len=3
0: ./wasi_args
1: 123
2: hello

$ ls -l
797K    wasi_args  # 기본 빌드
52K     wasi_args  # ReleaseSmall 옵션 빌드

$ zig build-exe src/wasi_args.zig -O ReleaseSmall

2. 피보나치 샘플 소스

표준 입력에서 입력값을 받아 피보나치 수열 결과를 출력하는 예제이다.

  • fibonacci : 함수 리커시브
  • print_fibonacci : fibonacci 함수를 호출하고 출력
    • 파라미터 w : 출력 handler
    • 파라미터 s : 입력 문자열(arg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const std = @import("std");

fn fibonacci(i: u64) u64 {
    if (i <= 1) return i;
    return fibonacci(i - 1) + fibonacci(i - 2);
}

fn print_fibonacci(w: anytype, s: []const u8) !void {
    const i = try std.fmt.parseUnsigned(u64, s, 10);
    try w.print("Fibonacci sequence number at index {d} is {d}\n", .{i, fibonacci(i)});
}

pub fn main() !void {
    // 메모리 할당자 선언
    const alloc: std.mem.Allocator = std.heap.page_allocator;

    // 표준 입력 핸들러 (메모리 연결)
    var args = try std.process.argsAlloc(alloc);
    defer alloc.free(args);

    // 표준 출력 핸들러
    const stdout = std.io.getStdOut();
    defer stdout.close();

    // 쓰기를 위한 함수 (alias)
    const out = stdout.writer();

    try out.print("Zig - Fibonacci sequence example\n", .{});

    // args 만 슬라이싱 하기 (0번째는 binary 이름)
    const indexes = args[1..];

    // 실행시 args 가 주어진 경우
    if (indexes.len > 0) {
        for (indexes) |arg| {
            try print_fibonacci(out, arg);
        }
    } 
    // args 가 없으면 표준 입력에서 받아온다
    else {
        // 표준 입력 핸들
        const stdin = std.io.getStdIn();
        defer stdin.close();

        try out.print("Enter a non-negative number:\n", .{});
        var buf: [19]u8 = undefined;
        if (try stdin.reader().readUntilDelimiterOrEof(&buf, '\n')) |arg| {
            try print_fibonacci(out, arg);
        } else {
            std.debug.print("failed to read from stdin", .{});
            std.process.exit(1);
        }
    }
}

빌드 및 실행

args 를 바로 추가하여 실행하거나, 표준 입력으로 받아서 실행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ zig run src/fibonacci.zig -- 10
Zig - Fibonacci sequence example
Fibonacci sequence number at index 10 is 55

$ zig run src/fibonacci.zig      
Zig - Fibonacci sequence example
Enter a non-negative number:
10
Fibonacci sequence number at index 10 is 55

# 바이너리 빌드
$ zig build-exe src/fibonacci.zig -O ReleaseFast

$ ./fibonacci 10       
Zig - Fibonacci sequence example
Fibonacci sequence number at index 10 is 55

$ ./fibonacci   
Zig - Fibonacci sequence example
Enter a non-negative number:
10
Fibonacci sequence number at index 10 is 55

WASM 빌드

wasm 바이너리를 WASM 런타임 wasmtime 으로 실행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
# wasm 바이너리 빌드
$ zig build-exe src/fibonacci.zig -target wasm32-wasi

$ wasmtime fibonacci.wasm 10                       
Zig - Fibonacci sequence example
Fibonacci sequence number at index 10 is 55

$ wasmtime fibonacci.wasm   
Zig - Fibonacci sequence example
Enter a non-negative number:
10
Fibonacci sequence number at index 10 is 55

WASI 란? (WebAssembly System Interface)

WebAssembly(WASM) 모듈이 브라우저 외부 환경에서 시스템 리소스에 접근할 수 있게 해주는 표준화된 시스템 인터페이스입니다.

  • 크로스플랫폼 실행 (클라우드, 임베디드 장치 등)
    • 시스템 리소스에 대한 표준화된 접근
    • OS 독립성
  • 서버 사이드 활용

참고 : WASM-Freestanding

표준 인터페이스인 WASI 를 사용하지 않고, 웹브라우저와 nodejs 만 사용한다면 OS 와 상관없이 간단하게 WASM 을 작성할 수 있다. 다음 문서에서 작성해 보자.

9. Review

  • Zig 를 공부하려던 이유는 WASM 때문이었다. zig 가 제일 최적화가 잘 될거 같아서였다. 그런데 이것 말고도 TinyGo 라는게 있다. 개발 생산성 측면에서는 TinyGo 가 나을듯 싶다.
  • WASI 를 이용한 WASM 을 크로스플랫폼 런타임에서 실행할 수 있다면, 이 또한 node 같은 플랫폼이 또 생긴다는 말 같은데.
  • wasmtime 은 Rust 언어쪽 문서에서도 자주 나온다. 이쪽도 wasm 에 진심이라서.

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.