Mark Zig code as zig

This commit is contained in:
Boris Verkhovskiy 2024-04-01 22:02:50 -07:00
parent c2878077cb
commit f9e182b42b

View File

@ -5,24 +5,19 @@ contributors:
- ["Philippe Pittoli", "https://karchnu.fr/"]
---
[Zig][ziglang] aims to be a replacement for the C programming language.
**WARNING**: this document expects you to understand a few basic concepts in computer science, such as pointers, stack and heap memory, etc.
**WARNING**: Zig isn't considered as ready for production. Bugs are expected.
DO NOT TRY ZIG AS YOUR FIRST PROGRAMMING EXPERIENCE.
The compiler, even the language and its libraries aren't ready, yet.
You've been warned.
Prior knowledge of C is recommended.
## Quick overview: Zig compared to C
- Syntax is mostly the same, with some improvements (less ambiguity).
- Zig introduces namespaces.
- Try and catch mechanism, which is both convenient, efficient and optional.
- `try` and `catch` mechanism, which is both convenient, efficient and optional.
- Most of the C undefined behaviors (UBs) are fixed.
- Compared to C, raw pointers are safer to use and less likely to be needed.
* The type system distinguishes between a pointer to a single value, or multiple values, etc.
@ -38,8 +33,7 @@ Prior knowledge of C is recommended.
## Zig language
```
```zig
//! Top-level documentation.
/// Documentation comment.
@ -47,9 +41,9 @@ Prior knowledge of C is recommended.
// Simple comment.
```
### Hello world.
```
```zig
// Import standard library, reachable through the "std" constant.
const std = @import("std");
@ -67,7 +61,8 @@ pub fn main() void {
```
### Booleans, integers and float.
```
```zig
// Booleans.
// Keywords are preferred to operators for boolean operations.
print("{}\n{}\n{}\n", .{
@ -109,7 +104,8 @@ i <<| 8 == 255 // u8: won't go higher than 255
```
### Arrays.
```
```zig
// An array is a well-defined structure with a length attribute (len).
// 5-byte array with undefined content (stack garbage).
@ -156,8 +152,8 @@ try some_integers[i]; // Runtime error 'index out of bounds'.
```
### Multidimensional arrays.
```
```zig
const mat4x4 = [4][4]f32{
[_]f32{ 1.0, 0.0, 0.0, 0.0 },
[_]f32{ 0.0, 1.0, 0.0, 1.0 },
@ -177,8 +173,8 @@ for (mat4x4) |row, row_index| {
```
### Strings.
```
```zig
// Simple string constant.
const greetings = "hello";
// ... which is equivalent to:
@ -195,8 +191,8 @@ print("string: {s}\n", .{greetings});
```
### Slices.
```
```zig
// A slice is a pointer and a size, an array without compile-time known size.
// Slices have runtime out-of-band verifications.
@ -206,8 +202,8 @@ const slice = array[0..array.len]; // "slice" represents the whole array.
```
### Pointers.
```
```zig
// Pointer on a value can be created with "&".
const x: i32 = 1;
const pointer: *i32 = &x; // "pointer" is a pointer on the i32 var "x".
@ -223,7 +219,8 @@ const foo = pointer.?; // Get the pointed value, otherwise crash.
```
### Optional values (?<type>).
```
```zig
// An optional is a value than can be of any type or null.
// Example: "optional_value" can either be "null" or an unsigned 32-bit integer.
@ -239,7 +236,8 @@ if (x) |value| {
```
### Errors.
```
```zig
// Zig provides an unified way to express errors.
// Errors are defined in error enumerations, example:
@ -299,7 +297,7 @@ var value = try some_function();
### Control flow.
```
```zig
// Conditional branching.
if (condition) {
@ -384,8 +382,8 @@ const result = for (items) |value| {
```
### Labels.
```
```zig
// Labels are a way to name an instruction, a location in the code.
// Labels can be used to "continue" or "break" in a nested loop.
outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| {
@ -434,8 +432,8 @@ const result = for (items) |value| { // First: loop.
```
### Switch.
```
```zig
// As a switch in C, but slightly more advanced.
// Syntax:
// switch (value) {
@ -454,15 +452,15 @@ var x = switch(value) {
// A slightly more advanced switch, accepting a range of values:
const foo: i32 = 0;
const bar = switch (foo) {
0 => "zero",
1...std.math.maxInt(i32) => "positive",
else => "negative",
0 => "zero",
1...std.math.maxInt(i32) => "positive",
else => "negative",
};
```
### Structures.
```
```zig
// Structure containing a single value.
const Full = struct {
number: u16,
@ -564,7 +562,8 @@ print("p.y: {}\n", .{p.y}); // 30
```
### Tuples.
```
```zig
// A tuple is a list of elements, possibly of different types.
const foo = .{ "hello", true, 42 };
@ -572,33 +571,33 @@ const foo = .{ "hello", true, 42 };
```
### Enumerations.
```
```zig
const Type = enum { ok, not_ok };
const CardinalDirections = enum { North, South, East, West };
const direction: CardinalDirections = .North;
const x = switch (direction) {
// shorthand for CardinalDirections.North
.North => true,
else => false
// shorthand for CardinalDirections.North
.North => true,
else => false
};
// Switch statements need exhaustiveness.
// WARNING: won't compile. East and West are missing.
const x = switch (direction) {
.North => true,
.South => true,
.North => true,
.South => true,
};
// Switch statements need exhaustiveness.
// Won't compile: East and West are missing.
const x = switch (direction) {
.North => true,
.South => true,
.East, // Its value is the same as the following pattern: false.
.West => false,
.North => true,
.South => true,
.East, // Its value is the same as the following pattern: false.
.West => false,
};
@ -606,12 +605,12 @@ const x = switch (direction) {
```
### Unions.
```
```zig
const Bar = union {
boolean: bool,
int: i16,
float: f32,
boolean: bool,
int: i16,
float: f32,
};
// Both syntaxes are equivalent.
@ -622,8 +621,8 @@ const foo: Bar = .{ .int = 42 };
```
### Tagged unions.
```
```zig
// Unions can be declared with an enum tag type, allowing them to be used in
// switch expressions.
@ -653,8 +652,8 @@ switch (nay) {
```
### Defer and errdefer.
```
```zig
// Make sure that an action (single instruction or block of code) is executed
// before the end of the scope (function, block of code).
// Even on error, that action will be executed.
@ -695,24 +694,25 @@ Thus, the standard library lets developers handle memory as they need, through s
**NOTE**: the choice of the allocator isn't in the scope of this document.
A whole book could be written about it.
However, here are some examples, to get an idea of what you can expect:
- page_allocator.
- `page_allocator`.
Allocate a whole page of memory each time we ask for some memory.
Very simple, very dumb, very wasteful.
- GeneralPurposeAllocator.
- `GeneralPurposeAllocator`.
Get some memory first and manage some buckets of memory in order to
reduce the number of allocations.
A bit complex. Can be combined with other allocators.
Can detect leaks and provide useful information to find them.
- FixedBufferAllocator.
- `FixedBufferAllocator`.
Use a fixed buffer to get its memory, don't ask memory to the kernel.
Very simple, limited and wasteful (can't deallocate), but very fast.
- ArenaAllocator.
- `ArenaAllocator`.
Allow to free all allocated memory at once.
To use in combinations with another allocator.
Very simple way of avoiding leaks.
A first example.
```
```zig
// "!void" means the function doesn't return any value except for errors.
// In this case we try to allocate memory, and this may fail.
fn foo() !void {
@ -735,8 +735,8 @@ fn foo() !void {
```
### Memory allocation combined with error management and defer.
```
```zig
fn some_memory_allocation_example() !void {
// Memory allocation may fail, so we "try" to allocate the memory and
// in case there is an error, the current function returns it.
@ -759,8 +759,8 @@ fn some_memory_allocation_example() !void {
```
### Memory allocators: a taste of the standard library.
```
```zig
// Allocators: 4 main functions to know
// single_value = create (type)
// destroy (single_value)
@ -846,8 +846,8 @@ fn gpa_arena_allocator_fn() !void {
```
### Comptime.
```
```zig
// Comptime is a way to avoid the pre-processor.
// The idea is simple: run code at compilation.
@ -883,7 +883,8 @@ list.items[0] = 10;
```
### Conditional compilation.
```
```zig
const available_os = enum { OpenBSD, Linux };
const myos = available_os.OpenBSD;
@ -905,7 +906,8 @@ const myprint = switch(myos) {
```
### Testing our functions.
```
```zig
const std = @import("std");
const expect = std.testing.expect;
@ -936,45 +938,44 @@ There are more than a hundred built-ins, allowing very low-level stuff:
- etc.
Example: enums aren't integers, they have to be converted with a built-in.
```
```zig
const Value = enum { zero, stuff, blah };
if (@enumToInt(Value.zero) == 0) { ... }
if (@enumToInt(Value.stuff) == 1) { ... }
if (@enumToInt(Value.blah) == 2) { ... }
```
### A few "not yourself in the foot" measures in the Zig language.
- Namespaces: names conflicts are easily avoided.
In practice, that means an unified API between different structures (data types).
- Namespaces: name conflicts are easily avoided.
In practice, that means a unified API between different structures (data types).
- Enumerations aren't integers. Comparing an enumeration to an integer requires a conversion.
- Explicit casts, coercion exists but is limited.
Types are slightly more enforced than in C, just a taste:
Pointers aren't integers, explicit conversion is necessary.
You won't lose precision by accident, implicit coercions are only authorized in case no precision can be lost.
Unions cannot be reinterpreted (in an union with an integer and a float, one cannot take a value for another by accident).
You won't lose precision by accident, implicit coercions are only authorized in cases where no precision can be lost.
Unions cannot be reinterpreted (in a union with an integer and a float, one cannot take a value for another by accident).
Etc.
- Removing most of the C undefined behaviors (UBs), and when the compiler encounters one, it stops.
- Slice and Array structures are preferred to pointers.
Types enforced by the compiler are less prone to errors than pointer manipulations.
- Numerical overflows produce an error, unless explicitly accepted using wrapping operators.
- Try and catch mechanism.
- `try` and `catch` mechanism.
It's both handy, trivially implemented (simple error enumeration), and it takes almost no space nor computation time.
- Unused variables are considered as errors by the compiler.
- Many pointer types exist in order to represent what is pointed.
- Unused variables are considered to be errors by the compiler.
- Many pointer types exist in order to represent what is pointed to.
Example: is this a single value or an array, is the length known, etc.
- Structures need a value for their attributes, and it still is possible to give an undefined value (stack garbage), but at least it is explicitly undefined.
- Structures need a value for their attributes, and it is still possible to give an undefined value (stack garbage), but at least it is explicitly undefined.
## Further Reading
For a start, some concepts are presented on the [Zig learn website][ziglearn].
For a start, some concepts are presented on [zig.guide][zigguide].
The [official website][zigdoc] provides a reference documentation to the language.
For now, documentation for standard library is WIP.
The [official website][zigdoc] provides the reference documentation of the language. The standard library [has its own documentation][zigstd].
[ziglang]: https://ziglang.org
[ziglearn]: https://ziglearn.org/
[zigguide]: https://zig.guide/
[zigdoc]: https://ziglang.org/documentation/
[zigstd]: https://ziglang.org/documentation/master/std/