How to learn available methods on a type/struct?

Just installed zig and try out the hello world example which includes
a call to print but I made error

hello.zig:5:15: error: member function expected 2 argument(s), found 1
    try stdout.print("Hello zig world!\n");

which led me to check out docs, so I go to std:fs.File.Writer definition
and ultimately end up with this single line of code

const Writer = field_call;

Hence the question, how does one typically go about learning all the methods available on a type/struct in Zig?

Hello @arhyth, welcome to the forum :slight_smile:

I am of the opinion that the best way to learn Zig is to read the source code. It’s very approachable compared to many other standard libraries out there, and it will help you rapidly advance in your knowledge. I’ll show you two popular ways to get around the source.


if you have not checked out ZLS, please do. It’s an auto-completer that has the ability to jump to definitions. That’s the fastest way to get to the definition of a struct in the source code and read what methods it has.

It’s still being developed but it’s the defacto lsp for Zig at the moment and I find it to be quite approachable and handy. If you need help getting it built and installed, the readme is fairly good (it’s a really quick process). In short:

git clone
cd zls
zig build -Doptimize=ReleaseSafe

Then move the binary into your path. I’m on ubuntu and I use helix, so for me that was as simple as appending the following line to my .bashrc…

# ZLS support for ZIG LSP in Helix
export PATH="/home/andrew/zls/zig-out/bin:$PATH"

At that point, you’ll have auto-complete and go-to-definition capabilities - if I jump to the struct definition itself, it takes me right to the source on my own computer.

The next best way to learn about methods is to read the associated file on: GitHub - ziglang/zig: General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.

The standard library is very easy to read compared to other languages. There are some more difficult patches than others, but we’re always here to help.

For standard utilities, we’ll go to the std folder:

Here, you’ll find the std import file that gets brought in when you call @import("std"):

For the writer, let’s check out the writer file:

You can search for the fn keyword and find most of what you’re looking for that way. Here’s all the member functions:

        pub fn write(self: Self, bytes: []const u8) Error!usize {
            return writeFn(self.context, bytes);

        pub fn writeAll(self: Self, bytes: []const u8) Error!void {
            var index: usize = 0;
            while (index != bytes.len) {
                index += try self.write(bytes[index..]);

        pub fn print(self: Self, comptime format: []const u8, args: anytype) Error!void {
            return std.fmt.format(self, format, args);

        pub fn writeByte(self: Self, byte: u8) Error!void {
            const array = [1]u8{byte};
            return self.writeAll(&array);

        pub fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void {
            var bytes: [256]u8 = undefined;
            @memset(bytes[0..], byte);

            var remaining: usize = n;
            while (remaining > 0) {
                const to_write = @min(remaining, bytes.len);
                try self.writeAll(bytes[0..to_write]);
                remaining -= to_write;

        /// Write a native-endian integer.
        pub fn writeIntNative(self: Self, comptime T: type, value: T) Error!void {
            var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
            mem.writeIntNative(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
            return self.writeAll(&bytes);

        /// Write a foreign-endian integer.
        pub fn writeIntForeign(self: Self, comptime T: type, value: T) Error!void {
            var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
            mem.writeIntForeign(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
            return self.writeAll(&bytes);

        pub fn writeIntLittle(self: Self, comptime T: type, value: T) Error!void {
            var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
            mem.writeIntLittle(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
            return self.writeAll(&bytes);

        pub fn writeIntBig(self: Self, comptime T: type, value: T) Error!void {
            var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
            mem.writeIntBig(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
            return self.writeAll(&bytes);

        pub fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
            var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
            mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
            return self.writeAll(&bytes);

        pub fn writeStruct(self: Self, value: anytype) Error!void {
            // Only extern and packed structs have defined in-memory layout.
            comptime assert(@typeInfo(@TypeOf(value)).Struct.layout != .Auto);
            return self.writeAll(mem.asBytes(&value));

Again, I strongly urge that you read the standard library. It’s very handy, well written, and (in my opinion) better than the documentation.

I hope some of this helps!


@arhyth I’m going to mark my previous answer as the solution to this thread (marking solutions helps us do analysis on the forum to evaluate how we’re doing). If you have any objections to this, let me know and I’ll open it back up for you :slight_smile:

– edited, removed solution, see below –

@AndrewCodeDev Sure thing. I was hoping others would offer more answers. But as there are none, it seems “this is the way”. :slightly_smiling_face:

I also concur that reading the source is probably the best! To maybe add a couple of tiny details:

In general, “how the API works?” can be a harder question to answer in Zig than in other languages, as function signatures do not always tell you the whole story, due to arguments like T: type or x: anytype, and due to the amount of computation that’s happening in the signature. std.mem.asBytes would be a good example of a pretty opaque API:

It still is possible to puzzle this out by reading the implementation of AsBytesReturnType, but that’s some explicit work.

The second thing is that I’ve personally found it quite useful to have Zig sources checked out locally. I pretty regularly go to my ~/projects/zig and look at things like lib/std or lib/build_runner. “Read the source” is of course not a satisfying answer long term, but, at the current stage of Zig’s life, its one of the best things to do. Note that there’s an extra point here — with Zig being young, it’s actually relatively easy to read the source of the standard library. In general, as software projects mature, their source code becomes harder and harder to read (because it handles progressively more and more niche concerns), and it makes sense to read the things as they are being written.


I think this is a great contribution to the topic - I’ll unmark my answer as “solution” to open the floor to more comments if anyone wants to add more advice.

1 Like

We have a wiki page on how to read the stdlib source code.