📜 ⬆️ ⬇️

How does zig work?

From the translator: this post was published on the author’s blog on March 15, 2018. As the language evolves, its syntax may currently vary. Everything described refers to Zig 0.2.0, the current version of the language is Zig 0.3.0.

I contacted the author of the post, and he kindly provided a link to the repository with the current version of the source code of the project in Zig 0.3.0

Hello! Let's write the Brainfuck interpreter! “Why?” You may ask, but you will not find an answer here.

I'll do it on Zig .

Zig is ...


... a new programming language. He is still in beta, and is developing rapidly. If you’ve seen Zig code before, the code in this post may seem a little different. He is really different! Zig 0.2.0 just came out, coinciding with the release of LLVM 6 a few weeks ago, and includes many syntax changes and general language enhancements. Mostly, many “spells” have been replaced by keywords. See here for a deeper explanation of all the changes!
')
Zig is designed to be readable , and relatively intuitive for those familiar with compiled and typed languages, such as C, C ++, and, in some respects, Rust.

The code was compiled and tested with Zig 0.2.0, which is available right now, through various channels , including homebrew, if you are on OSX: brew install zig.

Let's start


To learn how Brainfuck works, look here . There is almost nothing to learn, but it is a turing-complete language, which means that you can write anything on it.

I posted the code here in case you want to see the final product or early commits.

Zig is a compiled language. When you compile a program, the resulting binary (if you compile the executable binary, not the library) must have a main function that denotes an input point.

So…

// main.zig fn main() void { } 

... and run ...

 $ zig build-exe main.zig 

... gives ...

 /zig/std/special/bootstrap.zig:70:33: error: 'main' is private /zigfuck/main.zig:2:1: note: declared here 

main must be declared public to be visible outside the module ...

 // main.zig pub fn main() void { } 

Let the brainfuck program use an array of 30,000 bytes as memory, I will make such an array.

 // main.zig pub fn main() void { const mem: [30000]u8; } 

I can declare a constant (const) or a variable (var). Here, I declared mem as an array of 30,000 unsigned (u) bytes (8 bits).

This is not compiled.

 /main.zig:3:5: error: variables must be initialized 

An equivalent C program would compile normally: I can declare a variable without initialization, but Zig forces me to make a decision now, at the time of declaration of the variable. I may not care what is written into it, but I have to explicitly indicate this. I will do this by initializing the variable to an undefined value.

 // main.zig pub fn main() void { const mem: [30000]u8 = undefined; } 

Initializing a variable with an undefined value makes no guarantees about the value of the variable in memory. This is the same as an uninitialized declaration of a variable in C, except that it is necessary to indicate this explicitly.

But maybe I care how to initialize this memory. Perhaps I want to have a guarantee that zeros or some arbitrary value are written there. In this case, I must also explicitly state this:

 // main.zig pub fn main() void { const mem = []u8{0} ** 30000; } 

It may seem strange, but ** is the operator used to extend arrays. I declare an array of 0 bytes, and then extend it by 30,000, and get the final initialization value of 30,000 zero bytes. This operation occurs once at compile time . comptime is one of Zig's big ideas, and I’ll get back to it in one of the following posts.

Now let's write a program on brainfuck, which does nothing but increment the first memory slot five times!

 pub fn main() void { const mem = []u8{0} ** 30000; const src = "+++++"; } 

In Zig, strings are byte arrays. I should not declare src as a byte array, because the compiler implies this. This is optional, but if you want, then it is possible:

 const src: [5]u8 = "+++++"; 

This will compile normally. However, this:

 const src: [6]u8= "+++++"; 

will not be.

 main.zig:5:22: error: expected type '[6]u8', found '[5]u8' 

Another note: since strings are just arrays, they do not end in zero. However, you can declare a null-terminated string C. As a literal, it will look like this:

 c"Hello I am a null terminated string"; 

For the common good ...


I want to do something with each character in the string. I can do it! At the beginning of main.zig, I import some functions from the standard library:

 const warn = @import("std").debug.warn; 

import , like virtually everything that starts with the @ sign, is a built-in compiler function . Such features are always globally available. The import here works like javascript - you can import anything by digging into the namespace and extract from there any publicly available functions or variables. In the example above, I directly import the warn function and assign it, suddenly, to the constant constant. Now you can call it. This is a common pattern: we import directly from the std namespace and then either call std.debug.warn () or assign it to the variable warn. It looks like this:

 const std = @import("std"); const warn = std.debug.warn; 

 const warn = @import("std").debug.warn; // main.zig pub fn main() void { const mem = []u8{0} ** 30000; const src = "+++++"; for (src) |c| { warn("{}", c); } } 

During debugging and initial development and testing, I just want to print something on the screen. Zig is error-prone , and stdout is also prone to errors. I don't want to do this right now, and I can type directly into stderr with warn, which we imported from the standard library.

warn accepts a string with formatting like printf in C! The code above will print:

 4343434343 

43 - ascii character code +. I can also write:

 warn("{c}", c); 

and to get:

 +++++ 

So, we initialized the memory space, and wrote the program. Now we are implementing the language itself. I'll start with +, and replace the body of the for loop with switch:

 for (src) |c| { switch(c) { '+' => mem[0] += 1 } } 

I get two errors:

 /main.zig:10:7: error: switch must handle all possibilities switch(c) { ^ /main.zig:11:25: error: cannot assign to constant '+' => mem[0] += 1 ^ 

Of course, I can not assign a new value to a variable, which is a constant! mem needs to be made a variable ...

 var mem = []u8{0} ** 30000; 

as in the case of other errors, my switch construction should know what to do if the character is not equal to +, even if nothing needs to be done. In my case, this is exactly what I want. I fill this case with an empty block:

 for (src) |c| { switch(c) { '+' => mem[0] += 1, else => {} } } 

Now I can compile the program. Call at the end of warn and run:

 const warn = @import("std").debug.warn; pub fn main() void { var mem = []u8{0} ** 30000; const src = "+++++"; for (src) |c| { switch(c) { '+' => mem[0] += 1, else => {} } } warn("{}", mem[0]); } 

I get the number 5 printed in stderr , as I expected.

Go ahead…


Similarly, doing support.

 switch(c) { '+' => mem[0] += 1, '-' => mem[0] -= 1, else => {} } 

To use the> and <you need to use an additional variable that serves as a "pointer" in memory, which I allocated for the custom brainfuck program.

 var memptr: u16 = 0; 

Since an unsigned 16-bit can be a maximum of 65535, it is more than enough to index 30,000 bytes of address space.

in fact, 15 bits would be enough for us, which allows us to address 32767 bytes. Zig allows for types with different widths , but not u15 yet.

you can actually do u15 like this:

 const u15 = @IntType(false, 15): 

It is proposed to make any [iu] \ d + type be valid as an integer type.

Now instead of using mem [0], I can use this variable.

 '+' => mem[memptr] += 1, '-' => mem[memptr] -= 1, 

<and> simply increment and decrement this pointer.

 '>' => memptr += 1, '<' => memptr -= 1, 

Wonderful. We can now write a real program!

Checking 1,2,3


Zig has a built-in test mechanism. Anywhere in any file I can write a test block:

 test "Name of Test" { // test code } 

and run the test from the command line: zig test $ FILENAME. The rest of the test blocks are the same as normal code.

Let's look at this:

 // test.zig test "testing tests" {} zig test test.zig Test 1/1 testing tests...OK 

Of course, an empty test is useless. I can use assert to actually confirm the execution of the tests.

 const assert = @import("std").debug.assert; test "test true" { assert(true); } test "test false" { assert(false); } 

 zig test test.zig "thing.zig" 10L, 127C written :!zig test thing.zig Test 1/2 test true...OK Test 2/2 test false...assertion failure [37;1m_panic.7 [0m: [2m0x0000000105260f34 in ??? (???) [0m [37;1m_panic [0m: [2m0x0000000105260d6b in ??? (???) [0m [37;1m_assert [0m: [2m0x0000000105260619 in ??? (???) [0m [37;1m_test false [0m: [2m0x0000000105260cfb in ??? (???) [0m [37;1m_main.0 [0m: [2m0x00000001052695ea in ??? (???) [0m [37;1m_callMain [0m: [2m0x0000000105269379 in ??? (???) [0m [37;1m_callMainWithArgs [0m: [2m0x00000001052692f9 in ??? (???) [0m [37;1m_main [0m: [2m0x0000000105269184 in ??? (???) [0m [37;1m??? [0m: [2m0x00007fff5c75c115 in ??? (???) [0m [37;1m??? [0m: [2m0x0000000000000001 in ??? (???) [0m 

The test fell. Use the following command to reproduce the error:

 ./zig-cache/test 

Stack trace on poppy while in development.

To test it effectively, I need to break it apart. Let's start with this:

 fn bf(src: []const u8, mem: [30000]u8) void { var memptr: u16 = 0; for (src) |c| { switch(c) { '+' => mem[memptr] += 1, '-' => mem[memptr] -= 1, '>' => memptr += 1, '<' => memptr -= 1, else => {} } } } pub fn main() void { var mem = []u8{0} ** 30000; const src = "+++++"; bf(src, mem); } 

It seems to be working, right?

But…

 /main.zig:1:29: error: type '[30000]u8' is not copyable; cannot pass by value 

This is described in https://github.com/zig-lang/zig/issues/733 .

Zig is strict about this. Complex types, and all objects that can be resized cannot be passed by value. This makes stack allocation predictable and logical, and avoids unnecessary copying. If you want to use the semantics of the transfer by value in your program, you can implement it yourself using your allocation strategy, but the language itself does not support this under normal circumstances.

A natural way to circumvent this limitation is to pass a pointer instead of a value (passing by reference). Zig uses a different strategy, slices. A slice is a pointer with a length attached to it and with checking for falling into the borders. The syntax in the function signature is:

 fn bf(src: []const u8, mem: []u8) void { ... } 

and when calling a function it looks like this:

 bf(src, mem[0..mem.len]); 

Note that I defined the upper bound simply by referring to the length of the array. There is an abbreviated form of recording for such cases:

 bf(src, mem[0..]); 

Now I can start writing tests that test the bf () function directly. For the time being, I will add test functions to the end of the file

 test "+" { var mem = []u8{0}; const src = "+++"; bf(src, mem[0..]); assert(mem[0] == 3); } 

I take a mem array from one byte and then check what should happen (a byte is incremented three times). It works!

 Test 1/1 +...OK 

"-" is checked in the same way:

 test "-" { var mem = []u8{0}; const src = "---"; bf(src, mem[0..]); assert(mem[0] == 253); } 

Does not work! When I try to subtract 1 from 0, I get ...

 Test 2/2 -...integer overflow 

mem is an array of unsigned bytes, and subtracting 1 from 0 causes an overflow. Again, Zig makes me explicitly declare what I want. In this case, I do not have to worry about overflow, in fact, I want it to occur, since we are dealing with modular arithmetic , in accordance with the brainfuck specification . This means that the decrement of the cell with the number 0 will give me 255, and the increment of the value 255 will give me 0.

Zig has several auxiliary arithmetic operations that offer guaranteed wrapping semantics .

 '+' => mem[memptr] +%= 1, '-' => mem[memptr] -%= 1, 

This solves the problem of overflowing the whole and does what I expected.

To test <and>, I move through a small array and check the value of the incremented cell:

 test ">" { var mem = []u8{0} ** 5; const src = ">>>+++"; bf(src, mem[0..]); assert(mem[3] == 3); } 

and…

 test "<" { var mem = []u8{0} ** 5; const src = ">>>+++<++<+"; bf(src, mem[0..]); assert(mem[3] == 3); assert(mem[2] == 2); assert(mem[1] == 1); } 

In the latter case, I can directly compare the result with a static array using ...

 const mem = std.mem; 

Recall that I have already imported std. In the example below, I use mem.eql in this namespace:

 test "<" { var storage = []u8{0} ** 5; const src = ">>>+++<++<+"; bf(src, storage[0..]); assert(mem.eql(u8, storage, []u8{ 0, 1, 2, 3, 0 })); } 

... and remember, string literals, these are just u8 arrays in zig, and I can put hexadecimal literals in them, i.e. The following code will work the same way!

 assert(mem.eql(u8, storage, "\x00\x01\x02\x03\x00")); 

Add "."! It simply prints the value of a byte in the cell indicated by the pointer as a character. Now I use warn, but later I will replace it with stdout. This is simply conceptual, but somewhat confusing in implementation. I will do it later!

 '.' => warn("{c}", storage[memptr]), 

Cycles
[and] - here the magic begins ...

[- if the value of the current cell is zero, skip steps to the closing bracket without executing the code.
] - if the value of the current cell is not zero, go back to the opening bracket and execute the code again.

This time I will start with the test, I will test them together (obviously, it does not make sense to test them separately). The first test case - the storage [2] cell should be empty, although the loop should increment it if it starts:

 test "[] skips execution and exits" { var storage = []u8{0} ** 3; const src = "+++++>[>+++++<-]"; bf(src, storage[0..]); assert(storage[0] == 5); assert(storage[1] == 0); assert(storage[2] == 0); } 

and I will create blanks for the switch statement:

 '[' => if (storage[memptr] == 0) { }, ']' => if (storage[memptr] == 0) { }, 

What to do now? You can use a naive approach. I just increment the src pointer until I find it]. But I cannot use for this a for loop in zig, it was created only for iterating over collections, without missing their elements. The appropriate construction here is while:

It was:

 var memptr: u16 = 0; for (src) |c| { switch(c) { ... } } 

it became ...

 var memptr: u16 = 0; var srcptr: u16 = 0; while (srcptr < src.len) { switch(src[srcptr]) { ... } srcptr += 1; } 

Now I can reassign the srcptr pointer in the middle of the block, and I will:

 '[' => if (storage[memptr] == 0) { while (src[srcptr] != ']') srcptr += 1; }, 

This satisfies the test "[] skips code execution and exits"
This satisfies the test “[] skips execution and exits”, although not quite reliably, as we shall see.

What about closing brackets? I think you can write simply by analogy:

 test "[] executes and exits" { var storage = []u8{0} ** 2; const src = "+++++[>+++++<-]"; bf(src, storage[0..]); assert(storage[0] == 0); assert(storage[1] == 25); } ']' => if (storage[memptr] != 0) { while (src[srcptr] != '[') srcptr -= 1; }, 

You can see what is happening ... A naive solution with two brackets has a fatal flaw and completely breaks down on nested loops. Consider the following:

 ++>[>++[-]++<-] 

The result should be {2, 0}, but the first open bracket just stupidly goes to the first closing bracket, and everything gets confused. Jump to the next closing bracket at the same level of nesting. It’s easy to add a depth counter and keep track of it as you move forward along the line. We do it in both directions:

 '[' => if (storage[memptr] == 0) { var depth:u16 = 1; srcptr += 1; while (depth > 0) { srcptr += 1; switch(src[srcptr]) { '[' => depth += 1, ']' => depth -= 1, else => {} } } }, ']' => if (storage[memptr] != 0) { var depth:u16 = 1; srcptr -= 1; while (depth > 0) { srcptr -= 1; switch(src[srcptr]) { '[' => depth -= 1, ']' => depth += 1, else => {} } } }, 

and relevant tests: note that src in both tests includes an internal loop.

 test "[] skips execution with internal braces and exits" { var storage = []u8{0} ** 2; const src = "++>[>++[-]++<-]"; try bf(src, storage[0..]); assert(storage[0] == 2); assert(storage[1] == 0); } test "[] executes with internal braces and exits" { var storage = []u8{0} ** 2; const src = "++[>++[-]++<-]"; try bf(src, storage[0..]); assert(storage[0] == 0); assert(storage[1] == 2); } 

Separately, we note, [-] - idiom brainfuck, meaning "reset this cell." You can see that it doesn’t matter what value the cell had at the beginning, it will decrement until it reaches 0, and then execution will continue.

Unlucky way


I did not count on the possibility that the program on bf would be broken. What happens if I submit the wrong input program to my interpreter? For example, simply [without a closing bracket, or <, which immediately goes beyond the memory array? (I can wrap the memory pointer, but it's better to treat it as an error).

I'm going to look a bit ahead and explain all the differences in the code. I will render the bf interpreter function into a separate file and also render the functionality of seekBack and seekForward into my own small functions.

 const warn = @import("std").debug.warn; const sub = @import("std").math.sub; fn seekBack(src: []const u8, srcptr: u16) !u16 { var depth:u16 = 1; var ptr: u16 = srcptr; while (depth > 0) { ptr = sub(u16, ptr, 1) catch return error.OutOfBounds; switch(src[ptr]) { '[' => depth -= 1, ']' => depth += 1, else => {} } } return ptr; } fn seekForward(src: []const u8, srcptr: u16) !u16 { var depth:u16 = 1; var ptr: u16 = srcptr; while (depth > 0) { ptr += 1; if (ptr >= src.len) return error.OutOfBounds; switch(src[ptr]) { '[' => depth += 1, ']' => depth -= 1, else => {} } } return ptr; } pub fn bf(src: []const u8, storage: []u8) !void { var memptr: u16 = 0; var srcptr: u16 = 0; while (srcptr < src.len) { switch(src[srcptr]) { '+' => storage[memptr] +%= 1, '-' => storage[memptr] -%= 1, '>' => memptr += 1, '<' => memptr -= 1, '[' => if (storage[memptr] == 0) srcptr = try seekForward(src, srcptr), ']' => if (storage[memptr] != 0) srcptr = try seekBack(src, srcptr), '.' => warn("{c}", storage[memptr]), else => {} } srcptr += 1; } } 

This makes the switch much easier to read, in my opinion, seekForward and seekBack look and work very similarly, and I was tempted to refactor them into something smarter and more compact, but in the end, they do different things and handle errors also in different ways. It is easier to copy and correct, and so it will be clearer. Also, I will correct seekForward later, at some point, perhaps in a later post.

I added a few important things! Note that all three functions now return a type! .. This is a new syntax for what used to be type% T (error union). This means that a function may return either some specific type or an error. When I try to call such a function, I must either use try before calling a function that forwards an error up the call stack if an error occurs, or use catch:

 const x = functionCall() catch {} 

Where I handle errors in the catch block. As written, catch can swallow any errors. This is bad practice, but here Zig makes us do it explicitly. If I catch an error in an empty block, I thereby affirm that either I do not think that an error can occur, or I do not need to process it. In practice, this can be something like a TODO, and in fact it is also very easy to make it explicit!

 const x = functionCall() catch { @panic("TODO") } 

Recall that such a case will never happen in a production code. I notify the compiler that I know what I am doing. If an error could occur, I would have to add error handling.

So, what kind of errors should I return from seekBack or seekForward?

In seekBack:

 ptr = sub(u16, ptr, 1) catch return error.OutOfBounds; 

I replaced the decrement of the pointer to use the sub function from std lib, which throws an overflow error if overflow occurs. I want to catch this error and return the error OutOfBounds instead, which I create here just by using it.

Zig errors are basically an array of error codes that are generated by the compiler when you use error. Something is a Error. They are guaranteed to be unique, and can be used as values ​​in a switch block.

I want to use OutOfBounds here because, semantically, if the memory pointer becomes less than zero, I ask the runtime to go beyond the memory space that I have allocated.

Similarly, in the seekForward function:

 if (ptr >= src.len) return error.OutOfBounds; 

In this case, if the pointer is larger than src.len, I catch the error here and return the same error.

when calling:

 '[' => if (storage[memptr] == 0) srcptr = try seekForward(src, srcptr), ']' => if (storage[memptr] != 0) srcptr = try seekBack(src, srcptr), 

I try to call these functions. If they are called successfully, they are executed correctly, and try returns the srcptr value. If they are unsuccessful, try stops the execution of the function and returns an error to the place where the whole bf function is called.

The call can be from the main!

 const bf = @import("./bf.zig").bf; // yes, hello const hello_world = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>."; pub fn main() void { storage = []u8{0} ** 30000; bf(hello_world, storage[0..]) catch {}; } 

I swallow this error here and should not do so, but note the important point, how easily zig can send errors up through the stack of calls. It is not the responsibility of the calling function to check every error case, but the compiler causes it to call every function that can complete with an error with try. This should always be done, even if errors are ignored!

The new try / catch syntax eliminates a lot of spells like %% and% that people dislike so much.

Now I have implemented 7 out of 8 characters of brainfuck, and this is enough to run a “meaningful” program.

"Meaningful" program


Here is the program:

 //   ,   const fib = "++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++>++++++++++++++++>>+<<[>>>>++++++++++<<[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>[<+>-]>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>+>>]<<<<<]>[-]>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<.>.>>[>>+<<-]>[>+<<+>-]>[<+>-]<<<-]<<++..."; 

Run ...

 pub fn main() void { storage = []u8{0} ** 30000; bf(fib, storage[0..]) catch {}; } 

voila!

 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 121, 98, 219, 

One memory comes back to me every time I think about the Fibonacci series ... I learned about it from PBS (Public Broadcasting Service, American non-profit television broadcasting service) in the 80s, and I always remember that. I thought it would be forgotten, but Youtube is a great thing .

How can I improve this?


I already hinted at a few TODOs. I should not have used stderr for output. I want to use stdout.

Every time I open the interpreter, I open the stream to stdout and type into it:

 const io = std.io; ... pub fn bf(src: []const u8, storage: []u8) !void { const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); ... '.' => stdout.print("{c}", storage[memptr]) catch unreachable, ... 

What is going on here? io.getStdOut(), ( catch unreachable — , !). , , , print. print , warn, . print , .

, stdout, stdout. Zig , , .

, , ? , , ? , ? , Zig !

, !

 const bf = @import("./bf.zig").bf; const warn = @import("std").debug.warn; const serpinsky = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ -<<<[ ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< ]>.>+[>>]>+ ] "; pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch unreachable; } 


, bf , !void. , main. , :

 const bf = @import("./bf.zig").bf; const warn = @import("std").debug.warn; const serpinsky = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ -<<<[ ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< ]>.>+[>>]>+ ] "; pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch |err| switch (err) { }; } 

!

 /Users/jfo/code/zigfuck/main.zig:7:46: error: error.OutOfBounds not handled in switch shell returned 1 

, bf ! , , stdout, bf. , , , try. , , , catch, try, , .

, :

 const io = std.io; ... pub fn bf(src: []const u8, storage: []u8) !void { const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); ... '.' => stdout.print("{c}", storage[memptr]) catch unreachable, ... 

:

 const io = std.io; ... pub fn bf(src: []const u8, storage: []u8) !void { const stdout = &(io.FileOutStream.init(&(try io.getStdOut())).stream); ... '.' => try stdout.print("{c}", storage[memptr]), ... 

:

 const bf = @import("./bf.zig").bf; const warn = @import("std").debug.warn; const serpinsky = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ -<<<[ ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< ]>.>+[>>]>+ ] "; pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch |err| switch (err) { }; } 

, , !

 /Users/jfo/code/zigfuck/main.zig:7:46: error: error.SystemResources not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.OperationAborted not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.IoPending not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.BrokenPipe not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.Unexpected not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.WouldBlock not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.FileClosed not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.DestinationAddressRequired not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.DiskQuota not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.FileTooBig not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.InputOutput not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.NoSpaceLeft not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.AccessDenied not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.OutOfBounds not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.NoStdHandles not handled in switch shell returned 1 

Zig , ! switch , , , , .

 pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch |err| switch (err) { error.OutOfBounds => @panic("Out Of Bounds!"), else => @panic("IO error") }; } 

- , , , Zig, ! , ! !

Todo


, ! , , ",", brainfuck- getc, . , bf. , , Zig. , , , .

Conclusion


, , Zig . Zig , , , , , ++. , , . , , . Zig , , , .

Zig, , 0.2.0 ! , , debug-, , ! --release-fast --release-safe, . .

Zig. , 1.0.0, Zig, , , !

, #zig freenode , .

Source: https://habr.com/ru/post/435574/


All Articles