authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-01 19:45:08+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-22 21:01:44+00:00
logcc64295a6313f8697ed02143390caa8fe3a63626
tree510c0d33e7d9b079f30b39b5c057b2081f035218
parentf0b331e95a45db5eb435c09c225471be0dac2b69
signaturelock-open Commit is signed but in an unrecognized format.

langref: document modules, root source files, etc


6 files changed, 243 insertions(+), 11 deletions(-)

doc/langref.html.in+152-11
......@@ -381,7 +381,7 @@
381381 In this case, the {#syntax#}!{#endsyntax#} may be omitted from the return
382382 type of <code>main</code> because no errors are returned from the function.
383383 </p>
384 {#see_also|Values|Tuples|@import|Errors|Root Source File|Source Encoding|try#}
384 {#see_also|Values|Tuples|@import|Errors|Entry Point|Source Encoding|try#}
385385 {#header_close#}
386386 {#header_open|Comments#}
387387 <p>
......@@ -823,7 +823,7 @@
823823 <kbd>zig test</kbd> is a tool that creates and runs a test build. By default, it builds and runs an
824824 executable program using the <em>default test runner</em> provided by the {#link|Zig Standard Library#}
825825 as its main entry point. During the build, {#syntax#}test{#endsyntax#} declarations found while
826 {#link|resolving|Root Source File#} the given Zig source file are included for the default test runner
826 {#link|resolving|File and Declaration Discovery#} the given Zig source file are included for the default test runner
827827 to run and report on.
828828 </p>
829829 <aside>
......@@ -5222,7 +5222,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
52225222 <li>From library code, calling the programmer's panic function if they exposed one in the root source file.</li>
52235223 <li>When mixing C and Zig code, calling the canonical panic implementation across multiple .o files.</li>
52245224 </ul>
5225 {#see_also|Root Source File#}
5225 {#see_also|Panic Handler#}
52265226 {#header_close#}
52275227
52285228 {#header_open|@popCount#}
......@@ -6480,14 +6480,155 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
64806480 {#builtin#}
64816481 {#see_also|Build Mode#}
64826482 {#header_close#}
6483 {#header_open|Root Source File#}
6484 <p>TODO: explain how root source file finds other files</p>
6485 <p>TODO: pub fn main</p>
6486 <p>TODO: pub fn panic</p>
6487 <p>TODO: if linking with libc you can use export fn main</p>
6488 <p>TODO: order independent top level declarations</p>
6489 <p>TODO: lazy analysis</p>
6490 <p>TODO: using comptime { _ = @import() }</p>
6483 {#header_open|Compilation Model#}
6484 <p>
6485 A Zig compilation is separated into <em>modules</em>. Each module is a collection of Zig source files,
6486 one of which is the module's <em>root source file</em>. Each module can <em>depend</em> on any number of
6487 other modules, forming a directed graph (dependency loops between modules are allowed). If module A
6488 depends on module B, then any Zig source file in module A can import the <em>root source file</em> of
6489 module B using {#syntax#}@import{#endsyntax#} with the module's name. In essence, a module acts as an
6490 alias to import a Zig source file (which might exist in a completely separate part of the filesystem).
6491 </p>
6492 <p>
6493 A simple Zig program compiled with <code>zig build-exe</code> has two key modules: the one containing your
6494 code, known as the "main" or "root" module, and the standard library. Your module <em>depends on</em>
6495 the standard library module under the name "std", which is what allows you to write
6496 {#syntax#}@import("std"){#endsyntax#}! In fact, every single module in a Zig compilation &mdash; including
6497 the standard library itself &mdash; implicitly depends on the standard library module under the name "std".
6498 </p>
6499 <p>
6500 The "root module" (the one provided by you in the <code>zig build-exe</code> example) has a special
6501 property. Like the standard library, it is implicitly made available to all modules (including itself),
6502 this time under the name "root". So, {#syntax#}@import("root"){#endsyntax#} will always be equivalent to
6503 {#syntax#}@import{#endsyntax#} of your "main" source file (often, but not necessarily, named
6504 <code>main.zig</code>).
6505 </p>
6506 {#header_open|Source File Structs#}
6507 <p>
6508 Every Zig source file is implicitly a {#syntax#}struct{#endsyntax#} declaration; you can imagine that
6509 the file's contents are literally surrounded by {#syntax#}struct { ... }{#endsyntax#}. This means that
6510 as well as declarations, the top level of a file is permitted to contain fields:
6511 </p>
6512 {#code|TopLevelFields.zig#}
6513 <p>
6514 Such files can be instantiated just like any other {#syntax#}struct{#endsyntax#} type. A file's "root
6515 struct type" can be referred to within that file using {#link|@This#}.
6516 </p>
6517 {#header_close#}
6518 {#header_open|File and Declaration Discovery#}
6519 <p>
6520 Zig places importance on the concept of whether any piece of code is <em>semantically analyzed</em>; in
6521 essence, whether the compiler "looks at" it. What code is analyzed is based on what files and
6522 declarations are "discovered" from a certain point. This process of "discovery" is based on a simple set
6523 of recursive rules:
6524 </p>
6525 <ul>
6526 <li>If a call to {#syntax#}@import{#endsyntax#} is analyzed, the file being imported is analyzed.</li>
6527 <li>If a type (including a file) is analyzed, all {#syntax#}comptime{#endsyntax#}, {#syntax#}usingnamespace{#endsyntax#}, and {#syntax#}export{#endsyntax#} declarations within it are analyzed.</li>
6528 <li>If a type (including a file) is analyzed, and the compilation is for a {#link|test|Zig Test#}, and the module the type is within is the root module of the compilation, then all {#syntax#}test{#endsyntax#} declarations within it are also analyzed.</li>
6529 <li>If a reference to a named declaration (i.e. a usage of it) is analyzed, the declaration being referenced is analyzed. Declarations are order-independent, so this reference may be above or below the declaration being referenced, or even in another file entirely.</li>
6530 </ul>
6531 <p>
6532 That's it! Those rules define how Zig files and declarations are discovered. All that remains is to
6533 understand where this process <em>starts</em>.
6534 </p>
6535 <p>
6536 The answer to that is the root of the standard library: every Zig compilation begins by analyzing the
6537 file <code>lib/std/std.zig</code>. This file contains a {#syntax#}comptime{#endsyntax#} declaration
6538 which imports {#syntax#}lib/std/start.zig{#endsyntax#}, and that file in turn uses
6539 {#syntax#}@import("root"){#endsyntax#} to reference the "root module"; so, the file you provide as your
6540 main module's root source file is effectively also a root, because the standard library will always
6541 reference it.
6542 </p>
6543 <p>
6544 It is often desirable to make sure that certain declarations &mdash; particularly {#syntax#}test{#endsyntax#}
6545 or {#syntax#}export{#endsyntax#} declarations &mdash; are discovered. Based on the above rules, a common
6546 strategy for this is to use {#syntax#}@import{#endsyntax#} within a {#syntax#}comptime{#endsyntax#} or
6547 {#syntax#}test{#endsyntax#} block:
6548 </p>
6549 {#syntax_block|zig|force_file_discovery.zig#}
6550comptime {
6551 // This will ensure that the file 'api.zig' is always discovered (as long as this file is discovered).
6552 // It is useful if 'api.zig' contains important exported declarations.
6553 _ = @import("api.zig");
6554
6555 // We could also have a file which contains declarations we only want to export depending on a comptime
6556 // condition. In that case, we can use an `if` statement here:
6557 if (builtin.os.tag == .windows) {
6558 _ = @import("windows_api.zig");
6559 }
6560}
6561
6562test {
6563 // This will ensure that the file 'tests.zig' is always discovered (as long as this file is discovered),
6564 // if this compilation is a test. It is useful if 'tests.zig' contains tests we want to ensure are run.
6565 _ = @import("tests.zig");
6566
6567 // We could also have a file which contains tests we only want to run depending on a comptime condition.
6568 // In that case, we can use an `if` statement here:
6569 if (builtin.os.tag == .windows) {
6570 _ = @import("windows_tests.zig");
6571 }
6572}
6573
6574const builtin = @import("builtin");
6575 {#end_syntax_block#}
6576 {#header_close#}
6577 {#header_open|Special Root Declarations#}
6578 <p>
6579 Because the root module's root source file is always accessible using
6580 {#syntax#}@import("root"){#endsyntax#}, is is sometimes used by libraries &mdash; including the Zig Standard
6581 Library &mdash; as a place for the program to expose some "global" information to that library. The Zig
6582 Standard Library will look for several declarations in this file.
6583 </p>
6584 {#header_open|Entry Point#}
6585 <p>
6586 When building an executable, the most important thing to be looked up in this file is the program's
6587 <em>entry point</em>. Most commonly, this is a function named {#syntax#}main{#endsyntax#}, which
6588 {#syntax#}std.start{#endsyntax#} will call just after performing important initialization work.
6589 </p>
6590 <p>
6591 Alternatively, the presence of a declaration named {#syntax#}_start{#endsyntax#} (for instance,
6592 {#syntax#}pub const _start = {};{#endsyntax#}) will disable the default {#syntax#}std.start{#endsyntax#}
6593 logic, allowing your root source file to export a low-level entry point as needed.
6594 </p>
6595 {#code|entry_point.zig#}
6596 <p>
6597 If the Zig compilation links libc, the {#syntax#}main{#endsyntax#} function can optionally be an
6598 {#syntax#}export fn{#endsyntax#} which matches the signature of the C <code>main</code> function:
6599 </p>
6600 {#code|libc_export_entry_point.zig#}
6601 <p>
6602 {#syntax#}std.start{#endsyntax#} may also use other entry point declarations in certain situations, such
6603 as {#syntax#}wWinMain{#endsyntax#} or {#syntax#}EfiMain{#endsyntax#}. Refer to the
6604 {#syntax#}lib/std/start.zig{#endsyntax#} logic for details of these declarations.
6605 </p>
6606 {#header_close#}
6607 {#header_open|Standard Library Options#}
6608 <p>
6609 The standard library also looks for a declaration in the root module's root source file named
6610 {#syntax#}std_options{#endsyntax#}. If present, this declaration is expected to be a struct of type
6611 {#syntax#}std.Options{#endsyntax#}, and allows the program to customize some standard library
6612 functionality, such as the {#syntax#}std.log{#endsyntax#} implementation.
6613 </p>
6614 {#code|std_options.zig#}
6615 {#header_close#}
6616 {#header_open|Panic Handler#}
6617 <p>
6618 The Zig Standard Library looks for a declaration named {#syntax#}panic{#endsyntax#} in the root module's
6619 root source file. If present, it is expected to be a namespace (container type) with declarations
6620 providing different panic handlers.
6621 </p>
6622 <p>
6623 See {#syntax#}std.debug.simple_panic{#endsyntax#} for a basic implementation of this namespace.
6624 </p>
6625 <p>
6626 Overriding how the panic handler actually outputs messages, but keeping the formatted safety panics
6627 which are enabled by default, can be easily achieved with {#syntax#}std.debug.FullPanic{#endsyntax#}:
6628 </p>
6629 {#code|panic_handler.zig#}
6630 {#header_close#}
6631 {#header_close#}
64916632 {#header_close#}
64926633 {#header_open|Zig Build System#}
64936634 <p>
doc/langref/TopLevelFields.zig created+18
......@@ -0,0 +1,18 @@
1//! Because this file contains fields, it is a type which is intended to be instantiated, and so
2//! is named in TitleCase instead of snake_case by convention.
3
4foo: u32,
5bar: u64,
6
7/// `@This()` can be used to refer to this struct type. In files with fields, it is quite common to
8/// name the type here, so it can be easily referenced by other declarations in this file.
9const TopLevelFields = @This();
10
11pub fn init(val: u32) TopLevelFields {
12 return .{
13 .foo = val,
14 .bar = val * 10,
15 };
16}
17
18// syntax
doc/langref/entry_point.zig created+20
......@@ -0,0 +1,20 @@
1/// `std.start` imports this file using `@import("root")`, and uses this declaration as the program's
2/// user-provided entry point. It can return any of the following types:
3/// * `void`
4/// * `E!void`, for any error set `E`
5/// * `u8`
6/// * `E!u8`, for any error set `E`
7/// Returning a `void` value from this function will exit with code 0.
8/// Returning a `u8` value from this function will exit with the given status code.
9/// Returning an error value from this function will print an Error Return Trace and exit with code 1.
10pub fn main() void {
11 std.debug.print("Hello, World!\n", .{});
12}
13
14// If uncommented, this declaration would suppress the usual std.start logic, causing
15// the `main` declaration above to be ignored.
16//pub const _start = {};
17
18const std = @import("std");
19
20// exe=succeed
doc/langref/libc_export_entry_point.zig created+10
......@@ -0,0 +1,10 @@
1pub export fn main(argc: c_int, argv: [*]const [*:0]const u8) c_int {
2 const args = argv[0..@intCast(argc)];
3 std.debug.print("Hello! argv[0] is '{s}'\n", .{args[0]});
4 return 0;
5}
6
7const std = @import("std");
8
9// exe=succeed
10// link_libc
doc/langref/panic_handler.zig created+18
......@@ -0,0 +1,18 @@
1pub fn main() void {
2 @setRuntimeSafety(true);
3 var x: u8 = 255;
4 // Let's overflow this integer!
5 x += 1;
6}
7
8pub const panic = std.debug.FullPanic(myPanic);
9
10fn myPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
11 _ = first_trace_addr;
12 std.debug.print("Panic! {s}\n", .{msg});
13 std.process.exit(1);
14}
15
16const std = @import("std");
17
18// exe=fail
doc/langref/std_options.zig created+25
......@@ -0,0 +1,25 @@
1/// The presence of this declaration allows the program to override certain behaviors of the standard library.
2/// For a full list of available options, see the documentation for `std.Options`.
3pub const std_options: std.Options = .{
4 // By default, in safe build modes, the standard library will attach a segfault handler to the program to
5 // print a helpful stack trace if a segmentation fault occurs. Here, we can disable this, or even enable
6 // it in unsafe build modes.
7 .enable_segfault_handler = true,
8 // This is the logging function used by `std.log`.
9 .logFn = myLogFn,
10};
11
12fn myLogFn(
13 comptime level: std.log.Level,
14 comptime scope: @Type(.enum_literal),
15 comptime format: []const u8,
16 args: anytype,
17) void {
18 // We could do anything we want here!
19 // ...but actually, let's just call the default implementation.
20 std.log.defaultLog(level, scope, format, args);
21}
22
23const std = @import("std");
24
25// syntax