Skip to content

Instantly share code, notes, and snippets.

@ityonemo

ityonemo/test.md

Last active Jan 18, 2021
Embed
What would you like to do?
Zig in 30 minutes

A half-hour to learn Zig

This is inspired by https://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/

Basics

the command zig run my_code.zig will compile and immediately run your Zig program. Each of these cells contains a zig program that you can try to run (some of them contain compile-time errors that you can comment out to play with)

You'll want to declare a main() function to get started running code.

This program does almost nothing:

// comments look like this and go to the end of the line
pub fn main() void {}

You can import from the standard library by using the @import builtin and assigning the namespace to a const value. Almost everything in zig must be explicitly assigned its identifier. You can also import other zig files this way, and C files in a similar fashion with @cImport.

const std = @import("std");

pub fn main() void {
    std.debug.print("hello world!\n", .{});
}

Note:

  • I'll explain the funny second parameter in the print statement, later in the structs section.

var declares a variable, in most cases you should declare the variable type.

const std = @import("std");

pub fn main() void {
    var x: i32 = 47; // declares "x" of type i32 to be 47.
    std.debug.print("x: {}\n", .{x});
}

const declares that a variable's value is immutable.

pub fn main() void {
    const x: i32 = 47;
    x = 42; // error: cannot assign to constant
}

Zig is very picky and will NOT let you shadow identifiers from an outside scope, to keep you from being confused:

const x: i32 = 47;

pub fn main() void {
    var x: i32 = 42;  // error: redefinition of 'x'
}

Constants in the global scope are by default compile-time "comptime" values, and if you omit the type they are comptime typed and can turn into runtime types for your runtime values.

const x: i32 = 47;
const y = -47;  // comptime integer.

pub fn main() void {
    var a: i32 = y; // comptime constant coerced into correct type
    var b: i64 = y; // comptime constant coerced into correct type
    var c: u32 = y; // error: cannot cast negative value -47 to unsigned integer
}

You can explicitly choose to leave it undefined if it will get set later. Zig will fill in a dummy value with 0XAA bytes to help detect errors in debug mode if you cause an error from accidentally using it in debug.

const std = @import("std");

pub fn main() void {
  var x: i32 = undefined;
  std.debug.print("undefined: {}\n", .{x});
}

In some cases, zig will let you omit the type information if it can figure it out.

const std = @import("std");

pub fn main() void {
    var x: i32 = 47;
    var y: i32 = 47;
    var z = x + y; // declares z and sets it to 94.
    std.debug.print("z: {}\n", .{z});
}

But be careful, integer literals are comptime-typed, so this won't work:

pub fn main() void {
    var x = 47; // error: variable of type 'comptime_int' must be const or comptime
}

Functions

Here's a function (foo) that returns nothing. The pub keyword means that the function is exportable from the current scope, which is why main must be pub. You call functions just as you would in most programming languages:

const std = @import("std");

fn foo() void {
    std.debug.print("foo!\n", .{});

    //optional:
    return;
}

pub fn main() void {
    foo();
}

Here's a function that returns an integer value:

const std = @import("std");

fn foo() i32 {
    return 47;
}

pub fn main() void {
    var result = foo();
    std.debug.print("foo: {}\n", .{result});
}

Zig won't let you ignore return values for functions:

fn foo() i32 {
    return 47;
}

pub fn main() void {
    foo(); // error: expression value is ignored
}

but you can if you assign it to the throw-away _.

fn foo() i32 {
    return 47;
}

pub fn main() void {
  _ = foo();
}

You can make a function that can take a parameter by declaring its type:

const std = @import("std");

fn foo(x: i32) void {
    std.debug.print("foo param: {}\n", .{x});
}

pub fn main() void {
    foo(47);
}

Structs

structs are declared by assigning them a name using the const keyword, they can be assigned out of order, and they can be used by dereferencing with the usual dot syntax.

const std = @import("std");

const Vec2 = struct{
    x: f64,
    y: f64
};

pub fn main() void {
    var v = Vec2{.y = 1.0, .x = 2.0};
    std.debug.print("v: {}\n", .{v});
}

structs can have default values; structs can also be anonymous, and can coerce into another struct so long as all of the values can be figured out:

const std = @import("std");

const Vec3 = struct{
    x: f64 = 0.0,
    y: f64,
    z: f64
};

pub fn main() void {
    var v: Vec3 = .{.y = 0.1, .z = 0.2};  // ok
    var w: Vec3 = .{.y = 0.1}; // error: missing field: 'z'
    std.debug.print("v: {}\n", .{v});
}

You can drop functions into an struct to make it work like a OOP-style object. There is syntactic sugar where if you make the functions' first parameter be a pointer to the object, it can be called "Object-style", similar to how Python has the self-parametered member functions. The typical convention is to make this obvious by calling the variable self.

const std = @import("std");

const LikeAnObject = struct{
    value: i32,

    fn print(self: *LikeAnObject) void {
        std.debug.print("value: {}\n", .{self.value});
    }
};

pub fn main() void {
    var obj = LikeAnObject{.value = 47};
    obj.print();
}

By the way, that thing we've been passing into the std.debug.print's second parameter is a tuple. Without going into too much detail, it's an anonymous struct with number fields.
At compile time, std.debug.print figures out types of the parameters in that tuple and generates a version of itself tuned for the parameters string that you provided, and that's how zig knows how to make the contents of the print pretty.

const std = @import("std");

pub fn main() void {
    std.debug.print("{}\n", .{1, 2}); #  error: Unused arguments
}

Enums

Enums are declared by assigning the group of enums as a type using the const keyword.

Note:

  • In some cases you can shortcut the name of the Enum.
  • You can set the value of an Enum to an integer, but it does not automatically coerce, you have to use @enumToInt or @intToEnum to do conversions.
const std = @import("std");

const EnumType = enum{
    EnumOne,
    EnumTwo,
    EnumThree = 3
};

pub fn main() void {
    std.debug.print("One: {}\n", .{EnumType.EnumOne});
    std.debug.print("Two?: {}\n", .{EnumType.EnumTwo == .EnumTwo});
    std.debug.print("Three?: {}\n", .{@enumToInt(EnumType.EnumThree) == 3});
}

Arrays and Slices

zig has Arrays, which are contiguous memory with compile-time known length. You can initialize them by declaring the type up front and providing the list of values. You can access the length with the len field of the array.

Note:

  • Arrays in zig are zero-indexed.
const std = @import("std");

pub fn main() void {
    var array: [3]u32 = [3]u32{47, 47, 47};

    // also valid:
    // var array = [_]u32{47, 47, 47};

    var invalid = array[4]; // error: index 4 outside array of size 3.
    std.debug.print("array[0]: {}\n", .{array[0]});
    std.debug.print("length: {}\n", .{array.len});
}

zig also has slices, which are have run-time known length. You can construct slices from arrays or other slices using the slicing operation. Similarly to arrays, slices have a len field which tells you its length.

Note:

  • The interval parameter in the slicing operation is open (non-inclusive) on the big end.

Attempting to access beyond the range of the slice is a runtime panic (this means your program will crash).

const std = @import("std");

pub fn main() void {
    var array: [3]u32 = [_]u32{47, 47, 47};
    var slice: []u32 = array[0..2];

    // also valid:
    // var slice = array[0..2];

    var invalid = slice[3]; // panic: index out of bounds

    std.debug.print("slice[0]: {}\n", .{slice[0]});
    std.debug.print("length: {}\n", .{slice.len});
}

string literals are null-terminated utf-8 encoded arrays of const u8 bytes.
Unicode characters are only allowed in string literals and comments.

Note:

  • length does not include the null termination (officially called "sentinel termination")
  • it's safe to access the null terminator.
  • indices are by byte, not by unicode glyph.
const std = @import("std");
const string = "hello 世界";
const world = "world";

pub fn main() void {
    var slice: []const u8 = string[0..5];

    std.debug.print("string {}\n", .{string});
    std.debug.print("length {}\n", .{world.len});
    std.debug.print("null {}\n", .{world[5]});
    std.debug.print("slice {}\n", .{slice});
    std.debug.print("huh? {}\n", .{string[0..7]});
}

const arrays can be coerced into const slices.

const std = @import("std");

fn foo() []const u8 {  // note function returns a slice
    return "foo";      // but this is a const array.
}

pub fn main() void {
    std.debug.print("foo: {}\n", .{foo()});
}

Control structures

Zig gives you an if statement that works as you would expect.

const std = @import("std");

fn foo(v: i32) []const u8 {
    if (v < 0) {
        return "negative";
    }
    else {
        return "non-negative";
    }
}

pub fn main() void {
    std.debug.print("positive {}\n", .{foo(47)});
    std.debug.print("negative {}\n", .{foo(-47)});
}

as well as a switch statement

const std = @import("std");

fn foo(v: i32) []const u8 {
    switch (v) {
        0 => return "zero",
        else => return "nonzero"
    }
}

pub fn main() void {
    std.debug.print("47 {}\n", .{foo(47)});
    std.debug.print("0 {}\n", .{foo(0)});
}

Zig provides a for-loop that works only on arrays and slices.

const std = @import("std");

pub fn main() void {
    var array = [_]i32{47, 48, 49};

    for (array) | value | {
        std.debug.print("array {}\n", .{value});
    }
    for (array) | value, index | {
        std.debug.print("array {}:{}\n", .{index, value});
    }

    var slice = array[0..2];

    for (slice) | value | {
        std.debug.print("slice {}\n", .{value});
    }
    for (slice) | value, index | {
        std.debug.print("slice {}:{}\n", .{index, value});
    }
}

Zig provides a while-loop that also works as you might expect:

const std = @import("std");

pub fn main() void {
    var array = [_]i32{47, 48, 49};
    var index: u32 = 0;

    while (index < 2) {
        std.debug.print("value: {}\n", .{array[index]});
        index += 1;
    }
}

Error handling

Errors are special union types, you denote that a function can error by prepending ! to the front. You throw the error by simply returning it as if it were a normal return.

const MyError = error{
    GenericError,  // just a list of identifiers, like an enum.
    OtherError
};

pub fn main() !void {
    return MyError.GenericError;
}

If you write a function that can error, you must decide what to do with it when it returns. Two common options are try which is very lazy, and simply forwards the error to be the error for the function. catch explicitly handles the error.

  • try is just sugar for catch | err | {return err}
const std = @import("std");
const MyError = error{
    GenericError
};

fn foo(v: i32) !i32 {
    if (v == 42) return MyError.GenericError;
    return v;
}

pub fn main() !void {
    // catch traps and handles errors bubbling up
    _ = foo(42) catch |err| {
        std.debug.print("error: {}\n", .{err});
    };

    // try won't get activated here.
    std.debug.print("foo: {}\n", .{try foo(47)});

    // this will ultimately cause main to print an error trace and return nonzero
    _ = try foo(42);
}

You can also use if to check for errors.

const std = @import("std");
const MyError = error{
    GenericError
};

fn foo(v: i32) !i32 {
    if (v == 42) return MyError.GenericError;
    return v;
}

// note that it is safe for wrap_foo to not have an error ! because
// we handle ALL cases and don't return errors.
fn wrap_foo(v: i32) void {    
    if (foo(v)) | value | {
        std.debug.print("value: {}\n", .{value});
    } else | err | {
        std.debug.print("error: {}\n", .{err});
    }
}

pub fn main() void {
    wrap_foo(42);
    wrap_foo(47);
}

Pointers

Pointer types are declared by prepending * to the front of the type. No spiral declarations like C! They are dereferenced, with the .* field:

const std = @import("std");

pub fn printer(value: *i32) void {
    std.debug.print("pointer: {}\n", .{value});
    std.debug.print("value: {}\n", .{value.*});
}

pub fn main() void {
    var value: i32 = 47;
    printer(&value);
}

Note:

  • in Zig, pointers need to be aligned correctly with the alignment of the value it's pointing to.

For structs, similar to Java, you can dereference the pointer and get the field in one shot with the . operator. Note this only works with one level of indirection, so if you have a pointer to a pointer, you must dereference the outer pointer first.

const std = @import("std");

const MyStruct = struct {
    value: i32
};

pub fn printer(s: *MyStruct) void {
    std.debug.print("value: {}\n", .{s.value});
}

pub fn main() void {
    var value = MyStruct{.value = 47};
    printer(&value);
}

Zig allows any type (not just pointers) to be nullable, but note that they are unions of the base type and the special value null. To access the unwrapped optional type, use the .? field:

const std = @import("std");

pub fn main() void {
    var value: i32 = 47;
    var vptr: ?*i32 = &value;
    var throwaway1: ?*i32 = null;
    var throwaway2: *i32 = null; // error: expected type '*i32', found '(null)'

    std.debug.print("value: {}\n", .{vptr.*}); // error: attempt to dereference non-pointer type
    std.debug.print("value: {}\n", .{vptr.?.*});
}

Note:

  • when you use pointers from C ABI functions they are automatically converted to nullable pointers.

Another way of obtaining the unwrapped optional pointer is with the if statement:

const std = @import("std");

fn nullChoice(value: ?*i32) void {
    if (value) | v | {
        std.debug.print("value: {}\n", .{v.*});
    } else {
        std.debug.print("null!\n", .{});
    }
}

pub fn main() void {
    var value: i32 = 47;
    var vptr1: ?*i32 = &value;
    var vptr2: ?*i32 = null;

    nullChoice(vptr1);
    nullChoice(vptr2);
}

A taste of metaprogramming

Zig's metaprogramming is driven by a few basic concepts:

  • Types are valid values at compile-time
  • most runtime code will also work at compile-time.
  • struct field evaluation is compile-time duck-typed.
  • the zig standard library gives you tools to perform compile-time reflection.

Here's an example of multiple dispatch (though you have already seen this in action with std.debug.print, perhaps now you can imagine how it's implemented:

const std = @import("std");

fn foo(x : anytype) @TypeOf(x) {
    // note that this if statement happens at compile-time, not runtime.
    if (@TypeOf(x) == i64) {
        return x + 2;
    } else {
        return 2 * x;
    }
}

pub fn main() void {
    var x: i64 = 47;
    var y: i32 =  47;

    std.debug.print("i64-foo: {}\n", .{foo(x)});
    std.debug.print("i32-foo: {}\n", .{foo(y)});
}

Here's an example of generic types:

const std = @import("std");

fn Vec2Of(comptime T: type) type {
    return struct{
        x: T,
        y: T
    };
}

const V2i64 = Vec2Of(i64);
const V2f64 = Vec2Of(f64);

pub fn main() void {
    var vi = V2i64{.x = 47, .y = 47};
    var vf = V2f64{.x = 47.0, .y = 47.0};
    
    std.debug.print("i64 vector: {}\n", .{vi});
    std.debug.print("f64 vector: {}\n", .{vf});
}

From these concepts you can build very powerful generics!

The HEAP

Zig gives you many ways to interact with the heap, and usually requires you to be explicit about your choices. They all follow the same pattern:

  1. Create an Allocator factory struct.
  2. Retrieve the std.mem.Allocator struct creacted by the Allocator factory.
  3. Use the alloc/free and create/destroy functions to manipulate the heap.
  4. (optional) deinit the Allocator factory.

Whew! That sounds like a lot. But

  • this is to discourage you from using the heap.
  • it makes anything which calls the heap (which are fundamentally failable) obvious.
  • by being unopinionated, you can carefully tune your tradeoffs and use standard datastructures without having to rewrite the standard library.
  • you can run an extremely safe allocator in your tests and swap it out for a different allocator in release/prod.

Ok. But you can still be lazy. Do you miss just using jemalloc everywhere?
Just pick a global allocator and use that everywhere (being aware that some allocators are threadsafe and some allocators are not)! Please don't do this if you are writing a general purpose library.

In this example we'll use the std.heap.GeneralPurposeAllocator factory to create an allocator with a bunch of bells and whistles (including leak detection) and see how this comes together.

One last thing, this uses the defer keyword, which is a lot like go's defer keyword! There's also an errdefer keyword, but to learn more about that check the Zig docs (linked below).

const std = @import("std");

// factory type
const Gpa = std.heap.GeneralPurposeAllocator(.{});

pub fn main() !void {
    // instantiates the factory
    var gpa = Gpa{};
    
    // retrieves the created allocator.
    var galloc = &gpa.allocator;
    
    // scopes the lifetime of the allocator to this function and
    // performs cleanup; 
    defer _ = gpa.deinit();

    var slice = try galloc.alloc(i32, 2);
    // uncomment to remove memory leak warning
    // defer galloc.free(slice);
    
    var single = try galloc.create(i32);
    // defer gallo.destroy(single);

    slice[0] = 47;
    slice[1] = 48;
    single.* = 49;

    std.debug.print("slice: [{}, {}]\n", .{slice[0], slice[1]});
    std.debug.print("single: {}\n", .{single.*});
}

Coda

That's it! Now you know a fairly decent chunk of zig. Some (pretty important) things I didn't cover include:

  • tests! Dear god please write tests. Zig makes it easy to do it.
  • the standard library
    • the memory model (somewhat uniquely, zig is aggressively unopinionated about allocators)
  • async
  • cross-compilation
  • build.zig

For more details, check the latest documentation: https://ziglang.org/documentation/master/

or for a less half-baked tutorial, go to: https://ziglearn.org/

@mikeMoreno

This comment has been minimized.

Copy link

@mikeMoreno mikeMoreno commented Jan 3, 2021

Cool tutorial! In the generics example, did you mean to write?:

std.debug.print("i64 vector: {}\n", .{vi});
std.debug.print("f64 vector: {}\n", .{vf});
@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

yes, that's correct, sorry!

@RoyiAvital

This comment has been minimized.

Copy link

@RoyiAvital RoyiAvital commented Jan 3, 2021

This is really great!

2 remarks:

  • I think you should add, as a comment, the output of each print() example.
  • Could you explain the difference between using fn foo(x : anytype) @TypeOf(x) to fn foo(comptime T, x: T) @T for functions which supports multiple input types?
@kubkon

This comment has been minimized.

Copy link

@kubkon kubkon commented Jan 3, 2021

Great work! I’ve got a couple of nits if you’re interested:

  • I think it might be a good idea to explain that std.debug.print will be elided if the program is compiled in Release mode.
  • Perhaps it might be useful to say that a for block can be followed by an else? This comes in real handy and is a feature that is seldom seen in other languages.
  • The idiomatic way to increment the counter in a while loop is to use the syntax:
while (i < x) : (i += 1) { ... }
  • An incredible useful feature not found in other languages are named scopes/blocks which allow you to use break statement to escape the scope early:
const x = blk: {
    if (y == 10) break :blk 0;
    break :blk 1;
};
@painhardcore

This comment has been minimized.

Copy link

@painhardcore painhardcore commented Jan 3, 2021

Which version of zig is used?

@deltaphc

This comment has been minimized.

Copy link

@deltaphc deltaphc commented Jan 3, 2021

@kubkon

  • An incredible useful feature not found in other languages are named scopes/blocks which allow you to use break statement to escape the scope early:
const x = blk: {
    if (y == 10) break :blk 0;
    break :blk 1;
};

Agreed that it's a neat feature, but it is not unique to Zig. (Mostly) equivalent example in Rust:

let x = {
    if y == 10 { 0 }
    else { 1 }
};

More technically equivalent:

let x = 'blk: loop {
    if y == 10 { break 'blk 0; }
    break 'blk 1;
};

But I'm not trying to downplay Zig or anything. It actually has a few things that I wish Rust had, but I just wanted to point out a factual inaccuracy.

@deltaphc

This comment has been minimized.

Copy link

@deltaphc deltaphc commented Jan 3, 2021

Good overview of Zig! I hope to have a deeper look into the language once it gets closer to 1.0. I've been keeping an eye on it.

@kubkon

This comment has been minimized.

Copy link

@kubkon kubkon commented Jan 3, 2021

@kubkon

  • An incredible useful feature not found in other languages are named scopes/blocks which allow you to use break statement to escape the scope early:
const x = blk: {
    if (y == 10) break :blk 0;
    break :blk 1;
};

Agreed that it's a neat feature, but it is not unique to Zig. (Mostly) equivalent example in Rust:

let x = {
    if y == 10 { 0 }
    else { 1 }
};

Actually, this only covers the simplified use case of having a binary choice in Rust. In Zig, you can break out of arbitrary code path which is not possible in Rust.

More technically equivalent:

let x = 'blk: loop {
    if y == 10 { break 'blk 0; }
    break 'blk 1;
};

And yet, you have to agree that this is an ugly hack in Rust since you have to resort to using a loop construct to achieve the same. In fact, I believe this is discouraged as non-idiomatic in Rust. ;-)

@grncdr

This comment has been minimized.

Copy link

@grncdr grncdr commented Jan 3, 2021

@89z it is (briefly) explained:

By the way, that thing we've been passing into the std.debug.print's second parameter an anonymous array. at compile time, the contents and types of the parameters are analyzed and matched up to the (compile time) format string that you provided, and that's how zig knows how to make the contents of the print pretty.

It's easy to miss though.


@ityonemo: really nice work, I've been curious about Zig but never really spent time on it, this size of intro is just right to get me to try something 👍

@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

Sorry it turned out that my anonymous array explanation was wrong! I updated the text to make it correct, and clearer. I should probably do a pointers section, huh?

@89z

This comment has been minimized.

Copy link

@89z 89z commented Jan 3, 2021

It looks like the explanation was totally removed. All I want to know is: what is .{}, and can it only be used literally, or can you also bind it to a variable? I would expect that these would be common questions, as nearly every single Zig example you will find is going to include whatever that item is.

@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

I moved it to structs section, where it belongs.

@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

@89z I think it's a bit out of scope since it's not something that I think most people will use that often, outside of using formatted printed statements (this is intended to be a really half-baked treatment to get people curious, hence the links at the bottom), however, if you are curious about the details, yes you can bind it to a variable, note the third print statement triggers a compiler error, but the first two are valid and do exactly what you would expect:

const std = @import("std");

pub fn main() void {
    var x = .{"foo", 34};
    std.debug.print("{}\n", .{x.@"0"});
    std.debug.print("{}\n", .{x.@"1"});
    std.debug.print("{}\n", .{x.@"2"}); // error: no member named '2' in struct...
}
@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

* I think you should add, as a comment, the output of each `print()` example.

I am purposefully leaving this out to encourage one to download zig and try them to see what happens

* Could you explain the difference between using `fn foo(x : anytype) @TypeOf(x)` to `fn foo(comptime T, x: T) @T` for functions which supports multiple input types?

Yep! The first function takes one parameter. The second function takes two parameters, I would say that the first one is true multiple dispatch, the second one is assisted multiple dispatch. I literally just made that term up just now. So don't take it too seriously.

@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

Which version of zig is used?

0.7.1

@89z

This comment has been minimized.

Copy link

@89z 89z commented Jan 3, 2021

@ityonemo wouldnt those better be described as Tuples, rather than Anonymous
structs? Quoting from the very page you linked from:

Rust has tuples, which you can think of as "fixed-length collections of values
of different types".

let pair = ('a', 17);
pair.0; // this is 'a'
pair.1; // this is 17

https://fasterthanli.me/articles/a-half-hour-to-learn-rust

@ityonemo

This comment has been minimized.

Copy link
Owner Author

@ityonemo ityonemo commented Jan 3, 2021

@89z good point! Thank you. In fact, zig calls them tuples too, though it is an internal designation that is synonymous to anonymous structs with numbered fields. Tuples are less of a mouthful, and it's not a wrong designation, so I put it in the doc.

@89z

This comment has been minimized.

Copy link

@89z 89z commented Jan 3, 2021

Thanks, that makes a lot more sense now. Zig uses Tuples because it doesnt support variadic functions.

@RoyiAvital

This comment has been minimized.

Copy link

@RoyiAvital RoyiAvital commented Jan 3, 2021

* I think you should add, as a comment, the output of each `print()` example.

I am purposefully leaving this out to encourage one to download zig and try them to see what happens

@ityonemo, I think people who got here are either really want to get into Zig and give them the result won't make a difference beside making your tutorial clearer. For most, it is a check up people get what they think they should get (You might surprise them, then curiosity will kick in).

* Could you explain the difference between using `fn foo(x : anytype) @TypeOf(x)` to `fn foo(comptime T, x: T) @T` for functions which supports multiple input types?

Yep! The first function takes one parameter. The second function takes two parameters, I would say that the first one is true multiple dispatch, the second one is assisted multiple dispatch. I literally just made that term up just now. So don't take it too seriously.

@ityonemo, Performance wise, are they different? Will the generated code be any different?

@pfgithub

This comment has been minimized.

Copy link

@pfgithub pfgithub commented Jan 4, 2021

@RoyiAvital

Performance wise, are they different? Will the generated code be any different?

No. Comptime values only exist at comptime. Debug build: https://godbolt.org/z/qe7sjK . Release build: https://godbolt.org/z/sbePcP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.