Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I just looked up a Hello World program from the Zig Wikipedia article:

    const std = @import("std");
    const File = std.Io.File;
    
    pub fn main(init: std.process.Init) !void {
        _ = try File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
    }
That's a lot to follow, just to output a plan-text message, especially after this line: "The primary goal of Zig is to be a better solution to the sorts of tasks that are currently solved with C. A primary concern in that respect is readability…"


I don’t think you can judge a programming language based on its “Hello World.” AppleSoft BASIC has this:

10 PRINT “Hello World”

Beautifully simple and readable. But it’s not a good language by modern standards.

In your example, I see a lot of complexity being surfaced: output streams, locals instead of globals, error handling. I don’t know Zig but all of those are things that are important to address, and I like that the example doesn’t sweep them under the rug in pursuit of a false readability.


Everything it's doing it clear and readable. It's just not as easy to write. It streams the bytes to stdout using the default IO interface, and it can fail.

Alternatively, here's a simpler version (prints to stderr).

    const std = @import("std");

    pub fn main() void {
        std.debug.print("Hello, world!\n", .{}); 
    }
In practice, you normally don't want to print messages to stdout. So the increased friction here actually pushes you in a better direction.


the given complicated version's complexity actually just comes from the fact that it's a "more correct" way to write a hello world program, as it manually acquires the stdout File object and acknowledges that printing to stdout can fail. the complexity has nothing to do with stdout vs stderr, you could just use `.stderr()` instead of `.stdout()` (same "friction", it's even the same number of characters). `std.debug.print` is only meant for debugging/development as it gets stderr for you and discards the errors that could happen when writing to stderr.


I meant that the convenient interface only works for stderr. So you aren't accidentally sending debug messages to stdout and if you want to send bytes to stdout you have to do so intentionally and use the right interface.


The issue is that most "hello world" programs are not correct.


While most hello worlds do not check that the message was printed (which I assume writeStreamingAll does for you), dismissing the rest of the differences as "the others aren't correct" isn't really accurate.

Explicitly passing IO in is a fine design choice, but it's not a correctness issue to say others are wrong to not do so.


>While most hello worlds do not check that the message was printed

Should they?


Really depends on what a hello world is meant to be a simplification of.

If hello world is meant to be a simplification of printing large amounts of text to a buffered standard out? Then yes, it probably should be checking errors.

If hello world is meant to be a simplification of low-volume debug logging to prove that code was reached (aka, printf debugging), then the simple alternative hello world using std.debug.print is what you want.

For such debug prints, you don't want any buffering, you don't want it mixed in with stdout (despite the name, stderr is not just for error messages), and you don't really need to check for errors. And std.debug.print does not return errors.


Also, it's easy to make a C program return the number of characters printed and doesn't hurt readability:

    #include <stdio.h>
    int main(void)
    {
        return printf("hello, world\n");
    }


Whats the point of evaluating technology from hello world programs?


Tbf, it's a useful indicator whether a language follows the "simple things should be simple, complex things should be possible" principle. The vanilla 'Hello World' should always be an example of the "simple things should be simple" part.


The typical Hello World implementation tends to reveal very little about the language, because their print/println/printf/whatever implementations have failure modes that are either impossible to handle or easily ignored (e.g. panicking, throwing exceptions or returning error codes which you can implicitly ignore without compilation error) which they frequently use to effectively hide the complexity inherent to the problem. Some examples of this:

The C Programming Language includes a Hello World example that calls printf without checking the return value and returns a success code from the main function regardless.

The first example I find when googling "java hello world" simply calls System.out.println and neglects to call System.out.checkError to see if it was successful before exiting with a success code. Some Java developers won't even know what I'm talking about here because it has never occurred to them that printing may fail in a way that can only be discovered through this weird checking mechanism.

Go's example from their getting started guide simply calls fmt.Println while ignoring the return values which include any error that may have occurred, and the program exits with a success code regardless.

The example from Rust by Example is at least correct and thorough in that it will predictably panic upon error when invoking the println! macro, which is documented, but will through that mechanism not give you the option to actually handle the error except by using a different mechanism which front-loads more of the complexity (e.g. writeln!(io::stdout(), "Hello World")? for something equivalent to the Zig example).

Of course for something as basic as Hello World it might be easy to tell whether it was successful through a quick glance at the output, but consider some of these limitations in a larger program.

So maybe there is more inherent complexity to this problem than a typical Hello World implementation will reveal. Add to that the complexity of Zig's new swappable I/O models and their Hello World isn't so absurd.


It's really easy to make a C hello world program that forwards the success of printf:

    #include <stdio.h>
    int main(void)
    {
        return printf("hello, world\n");
    }
I just tested it in a Bash shell, and it works great, only adding a single word, with clear functionality, to the example.


This program will always give a non-zero return code. This is unconventional if not straight up wrong, if the goal is for main to indicate whether it was successful in printing.


I mean sure, from a purely 'is this program correct' pov you're correct, but then a hello-world is mainly about "how do I get some frigging text to show up on the terminal", and how likely is that to fail anyway (at least I never had the canonical C hello-world fail on me).


What's the point of showing an example if it's incorrect? If someone asks "how do I get some frigging text to show up on the terminal" and the answer is incorrect, it's bad advice as far as I'm concerned.

> and how likely is that to fail anyway (at least I never had the canonical C hello-world fail on me).

I don't expect to know how likely writing to stdout is to fail and I don't think any answer to that question other than 0 really warrants ignoring the potential error if the correct result of invoking your program depends on it. For what it's worth, at least in Unix-likes, stdout could be pretty much anything. Writing to stdout could fail because a switch at the user's ISP is rebooting.


It shows the bare minimum overhead needed to start a project and gives some insight into the readability.


That's the 'official' hello world which is indeed a bit verbose (it's "correct" in the way that it generally shows how to stream formatted text to stdout though).

Arguably this is the more beginner friendly version, this prints to stderr though:

hello.zig:

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

    pub fn main() void {
        print("Hello World!\n", .{});
    }
...and then

    zig run hello.zig




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: