[HN Gopher] Bugs in Hello World
       ___________________________________________________________________
        
       Bugs in Hello World
        
       Author : sizediterable
       Score  : 483 points
       Date   : 2022-03-09 05:59 UTC (17 hours ago)
        
 (HTM) web link (blog.sunfishcode.online)
 (TXT) w3m dump (blog.sunfishcode.online)
        
       | sedatk wrote:
       | Did the author just assume the spec of Hello World?
        
       | edejong wrote:
       | Correct solution:                 printf '#include <stdio.h>\nint
       | main() { return printf("Hello world!\\n") && fflush(stdout); }\n'
       | | cc -xc - && ./a.out > /dev/full && echo "Success\!"
        
         | Someone wrote:
         | Shouldn't that be                 return (printf("Hello
         | world!\n") < 0) && fflush(stdout);
         | 
         | _printf_ returns the _"number of characters transmitted to the
         | output stream or negative value if an output error or an
         | encoding error (for string and character conversion specifiers)
         | occurred"_ , so it won't ever return zero for that call
         | (https://en.cppreference.com/w/c/io/fprintf)
         | 
         | I also think this optimally should do something like
         | int x = printf("Hello world!\n");       if(x<0) return x; //
         | maybe fflush here, too, ignoring errors?       return
         | fflush(stdout);
         | 
         | Logging an error message to _stderr_ should be considered, too.
         | I would ignore any errors from that, but attempting to _syslog_
         | those or to write them to the console could be a better choice.
        
       | sundarurfriend wrote:
       | Julia (1.7) behaves pretty similar to the Python 2 one, a printed
       | error related to closing the stream, and a 0 error code.
       | ~ >>> julia -e 'print("Hello world")' > /dev/full         error
       | in running finalizer: Base.SystemError(prefix="close", errnum=28,
       | extrainfo=nothing)         #systemerror#69 at ./error.jl:174
       | systemerror##kw at ./error.jl:174         systemerror##kw at
       | ./error.jl:174         #systemerror#68 at ./error.jl:173
       | [inlined]         systemerror at ./error.jl:173 [inlined]
       | close at ./iostream.jl:63         [?]              ~ >>> echo $?
       | 0
       | 
       | `errnum=28` apparently refers to the ENOSPC error: "No space left
       | on device" as defined by POSIX.1; so the information is there,
       | even if not in the most presentable form.
        
       | andai wrote:
       | An interesting link in the article: "Main is usually a function.
       | So then when is it not?"
       | 
       | https://news.ycombinator.com/item?id=27504254
        
       | bor0 wrote:
       | It's a good post, but heavily OS dependant. For example, on my
       | Mac:                 $ ls /dev/null /dev/full       ls:
       | /dev/full: No such file or directory       /dev/null
       | 
       | I guess in theory, you can imitate `/dev/full` by other means.
        
         | oneeyedpigeon wrote:
         | Yup -- I found this worked well:
         | https://www.thedroidsonroids.com/blog/dev-full-osx
        
         | PennRobotics wrote:
         | I can't test this and the original blog post seems to be
         | missing, but someone has a Github gist where they create a full
         | ramdisk on OSX:
         | 
         | https://gist.github.com/koral--/12a6cdda22ffbd82f28ecc93e0b5...
        
         | cyborgx7 wrote:
         | >Linux has this fun device file called "/dev/full"
         | 
         | Yes it is. And it specifies the OS as well.
        
           | not2b wrote:
           | NetBSD and FreeBSD have /dev/full as well.
        
             | pjerem wrote:
             | And all OS can have disks that are full as well.
        
       | kazinator wrote:
       | > _There 's our "No space" error getting reported by the OS, but
       | no matter, the program silently swallows it and returns 0, the
       | code for success. That's a bug!_
       | 
       | Bzzt, no. You can't say that without knowing what the program's
       | requirements are.
       | 
       | Blindly "fixing" a program to indicate failure due to not being
       | able to write to standard output could break something.
       | 
       | Maybe the output is just a diagnostic that's not important, but
       | some other program will reacts to the failed status, causing an
       | issue.
       | 
       | Also, if a program produces output with a well-defined syntax,
       | then the termination status may be superfluous; the truncation of
       | the output can be detected by virtue of that syntax being
       | incomplete.
       | 
       | E.g. JSON hello world fragment:
       | puts("{\"hello\":\"world\"}");        return 0;
       | 
       | if something is picking up the output and parsing it as JSON, it
       | can deduce from a failed parse that the program didn't complete,
       | rather than going by termination status.
        
         | jjnoakes wrote:
         | > if something is picking up the output and parsing it as JSON,
         | it can deduce from a failed parse that the program didn't
         | complete, rather than going by termination status.
         | 
         | This is bad advice. Consider output that might be truncated but
         | can't be detected (mentioned in the article).
         | 
         | The exit status is the only reliable way to detect failures
         | (unless you have a separate communication channel and send a
         | final success message).
        
           | kazinator wrote:
           | My remark "if a program produces output with a well-defined
           | syntax" was intended specifically to consider such cases, and
           | set them aside.
           | 
           | I didn't communicate that clearly: syntax can be "well-
           | defined" yet truncatable. I meant some kind of syntax that is
           | invalid if any suffix is missing, including the entire
           | message, or else an object of an unexpected type is produced.
           | 
           | (In the case of JSON, valid JSON could be output which is
           | truncatable, like 3.14 versus 3.14159. If the output is
           | documented and expected to be a dictionary, we declare
           | failure if a number emerges.)
        
         | MauranKilom wrote:
         | > Also, if a program produces output with a well-defined
         | syntax, then the termination status may be superfluous; the
         | truncation of the output can be detected by virtue of that
         | syntax being incomplete.
         | 
         | The author covers this (or rather, the possibility that
         | truncation can _not_ be detected).
        
         | kazinator wrote:
         | There is more nuance to this, which is that we cannot detect
         | all modes of failure just because we have written to a file
         | object, and successfully flushed and closed it.
         | 
         | In the case of file I/O, we do not know that the bits have
         | actually gone to the storage device. A military-grade hello
         | world has to perform a fsync. I think that also requires the
         | right storage hardware to be entirely reliable.
         | 
         | If stdout happens to be a TCP socket, then all we know from a
         | successful flush and close is that the data has gone into the
         | network stack, not that the other side has received it. We need
         | an end-to-end application level ack. (Even just a two-way
         | orderly shutdown: after writing hello, half-close the socket.
         | Then read from it until EOF. If the read fails, the connection
         | was broken and it cannot be assumed that the hello had been
         | received.)
         | 
         | This issue is just a facet of a more general problem: if the
         | goal of the hello world program is to communicate its message
         | to some destination, the only way to be sure is to obtain an
         | acknowledgement from that destination: communication must be
         | validated end-to-end, in other words. If you rely on any
         | success signal of an intermediate agent, you don't have end-to-
         | end validation of success.
         | 
         | The super-robust requirements for hello world therefore call
         | for a protocol: something like this:
         | puts("Hello, world!");         puts("message received OK?
         | [y/n]")         return (fgets(buffer, sizeof buffer, stdin) !=
         | NULL && buffer[0] == 'y')                 ? EXIT_SUCCESS :
         | EXIT_FAILURE;
         | 
         | Now we can detect failures like that there is no user present
         | at the console who is reading the message. Or that their
         | monitor isn't working so the can't read the question.
         | 
         | We can now correctly detect this case of not being able to
         | deliver hello, world, converting it to a failed status:
         | $ ./hello < /dev/null > /dev/null
         | 
         | We can still be lied to, but there is strong justification in
         | regarding that as not our problem:                 $ yes |
         | ./hello > /dev/null
         | 
         | We cannot get away from requiring syntax, because the presence
         | of a protocol gives rise to it; the destination has to be able
         | to tell somehow when it has received all of the data, so it can
         | acknowledge it.
         | 
         | A super reliable hello world also must not take data integrity
         | for granted; the message should include some kind of checksum
         | to reduce the likelihood of corrupt communication going
         | undetected.
        
         | ComradePhil wrote:
         | > You can't say that without knowing what the program's
         | requirements are.
         | 
         | The "program's requirements" can in theory be "to be buggy
         | unusable piece of shit". But when we speak, we don't need to
         | consider that use case.
        
       | mccorrinall wrote:
       | Imo this is because the responsibility is not clearly defined and
       | can be argued upon.
       | 
       | If my program writes to the standard output, but you choose to
       | redirect the pipe to a different location, is it my program's
       | responsibility to check what happens to the bytes AFTER the pipe?
       | 
       | After all: _my program did output everything as expected_. The
       | part which fucked up was not part of my program.
       | 
       | I can see why some projects decide to not handle this bug.
        
         | mirekrusin wrote:
         | Your program has a bug because it can write nothing or just
         | part and will always return zero exit code. Ie think about
         | using your program as part of bash script where you often rely
         | on process exit codes.
        
         | masklinn wrote:
         | > If my program writes to the standard output, but you choose
         | to redirect the pipe to a different location, is it my
         | program's responsibility to check what happens to the bytes
         | AFTER the pipe?
         | 
         | The pipe _is_ your standard output. Your very program is
         | created with the pipe as its stdout.
         | 
         | > After all: my program did output everything as expected. The
         | part which fucked up was not part of my program.
         | 
         | But you are wrong, your program did not output everything as
         | expected, and it failed to report that information.
        
         | ygra wrote:
         | I thought about this too a bit just now. But I think it's not
         | the shell setting up stuff outside your process that then
         | fails. Rather you already get handles to the "full" file system
         | at process creation and then it's your problem. And
         | traditionally, the behaviour you get from all the standard
         | streams is very unpredictable, depending on where they point.
        
         | usrbinbash wrote:
         | > but you choose to redirect the pipe to a different location
         | 
         | The output doesn't go into a pipe however, the output goes to
         | /dev/full. Redirection happens before the process is started,
         | so the program is, in fact, writing directly to a file
         | descriptor that returns an error upon write.
        
         | oneeyedpigeon wrote:
         | I feel like this is misrepresenting the article's point which
         | isn't literally "hello world is buggy if it returns success on
         | failure" but more "you should do error-handling". In this very
         | specific case, you can argue that it's irrelevant. But if your
         | program writes to a log file, or writes to a data file that it
         | later reads from, it had better include some error-handling.
         | 
         | The fact that there's redirection is a ... misdirection. The
         | redirection is only used to proxy a real-life case that can
         | happen even when no redirection is taking place.
        
           | autoexec wrote:
           | What's the real-life case where hello world fails because the
           | file system is full?
           | 
           | You could do all kinds of things that would cause hello world
           | to "fail". A broken monitor (or even one unplugged) wouldn't
           | show "hello world" or give any indication of an error too,
           | but it's hardly the codes fault. The code does what it's
           | supposed to and ignores all kinds of other things that could
           | go horribly wrong. That's not really a bug, just a known and
           | expected limitation of the program's scope.
        
             | oneeyedpigeon wrote:
             | As I said, not hello world necessarily, but any program
             | that writes output can encounter this problem, and there's
             | an overlap with general file-writing even.
        
         | dataflow wrote:
         | > is it my program's responsibility to check what happens to
         | the bytes AFTER the pipe?
         | 
         | No, but it's _not_ "after". Rather, it's your responsibility to
         | handle backpressure by ensuring the bytes were written to the
         | pipe successfully in the first place.
         | 
         | This isn't just about the filesystem being full btw. If you
         | imagine a command like ./foo.py | head -n 10, it only makes
         | sense for the 'head' command to close the pipe when it's done,
         | and foo.py should be able to detect this and stop printing any
         | more output. (This is especially important if you consider that
         | foo.py might produce _infinite_ lines of output, like the
         | 'yes' program.)
         | 
         | I would argue this is not necessarily even an error from a user
         | standpoint, so the return code from food.py should still be
         | zero in many cases--a pipe-is-closed error just means the
         | consumer simply didn't want the rest of the output, which is
         | fine [1], whereas an out-of-disk-space error is probably really
         | an error. Handling these robustly is actually difficult though,
         | because (a) you'd need to figure out _why_ printf() failed (so
         | that you can treat different failures differently--but it 's
         | painful), and (b) you need to make sure any side effects in the
         | program flow _up to the printf()_ are semantically correct
         | "prefixes" of the overall side effect, meaning that you'd need
         | to pay careful attention to _where_ you printf(). (Practically
         | speaking, this makes it difficult to even _have_ side effects
         | that respect this, but that 's an inherent problem/limitation
         | of the pipeline model...)
         | 
         | FWIW, I would be very curious if anyone has formalized all of
         | these nuances of the pipeline model and come up with a robust &
         | systematic way to handle them. It seems like a complicated
         | problem to me. To give just _one_ example of a problem that I
         | 'm thinking of: should stderr and stdout behave the same way
         | with respect to "pipe is closed"? e.g. should the program
         | terminate if _either_ is closed, or if _both_ are closed? The
         | answer is probably  "it depends", but on _what_ exactly? What
         | if they 're redirected externally? What if they're redirected
         | internally? Is there a pattern you can follow to get it right
         | most of the time? There's a lot of room for analysis of the
         | issues that can come up, especially when you throw
         | buffering/threading/etc. into the mix...
         | 
         | [1] Or maybe it isn't. Maybe the output (say, some archive
         | format like ZIP) has a footer that needs to be read first, and
         | it would be corrupt otherwise. Or maybe that's fine anyway,
         | because the consumer should already understand you're
         | outputting a ZIP, and it's on them if they want partial output.
         | As always, "it depends". But I think a premature stdout closure
         | is _usually_ best treated as not-an-error.
        
           | hgomersall wrote:
           | What the pipe does is orthogonal to what the programme should
           | do. The problem here is that errors are not being handled.
           | There are languages such as rust that enforce error handling,
           | whereby the policy on error is made explicit. The nuances you
           | highlight are around what the errors should describe, which
           | ultimately leads to more potential granularity in the error
           | policy.
        
           | hvdijk wrote:
           | > This isn't just about the filesystem being full btw. If you
           | imagine a command like ./foo.py | head -n 10, it only makes
           | sense for the 'head' command to close the pipe when it's
           | done, and foo.py should be able to detect this and stop
           | printing any more output.
           | 
           | The usual way of handling this is by not (explicitly)
           | handling it. Writes to a closed pipe are special, they do not
           | normally fail with a status that the program then all too
           | often ignores, they result in a SIGPIPE signal that defaults
           | to killing the process. Extra steps are needed to not kill
           | the process. No other kind of write error gets this special
           | treatment that I am aware of.
        
             | dataflow wrote:
             | That would be at best a Linux extension, not some general C
             | behavior you can assume when writing your program.
             | 
             | That said though, I can't even reproduce what you're saying
             | on Linux:                 printf '%s\n' '#include
             | <stdio.h>' 'int main() { setvbuf(stdout, NULL, _IONBF, 0);
             | int r = fputs("Starting\n", stdout); fflush(stdout);
             | fprintf(stderr, "%d\n", r); }' | cc -x c - && ./a.out >&-
             | // Prints '0' instead of dying
        
               | hvdijk wrote:
               | That's a POSIX thing. It doesn't apply to all C
               | implementations but it does apply to many more than just
               | Linux-based ones. You've not got a closed pipe so you
               | wouldn't see it, you've just got a closed file
               | descriptor. Try running it as                 ./a.out | :
               | 
               | and you will probably see it. I say probably because
               | there is a timing aspect as well, the write may happen
               | before the pipe gets closed in which case it will not
               | fail, but it is unlikely to.
        
               | dataflow wrote:
               | Yeah I should've said POSIX, my bad. But yeah my point
               | was it's not plain C behavior.
               | 
               | And yes on Linux I do see it with your no-op example now.
               | Though for some reason not with 'head'... what's going
               | on? Is it not closing the pipe when it exits?
               | $ printf '%s\n' '#include <stdio.h>' '#include
               | <unistd.h>' 'int main() { setvbuf(stdout, NULL, _IONBF,
               | 0); int r = puts("Starting...\n"); r += fputs("First\n",
               | stdout); fflush(stdout); usleep(1000000); fprintf(stderr,
               | "%d\n", r); }' | cc -x c - && ./a.out | head -n 1
               | Starting...       19
               | 
               | Edit: D'oh, see below.
        
               | hvdijk wrote:
               | That would be the timing aspect of it. You have:
               | 
               | a) a.out writes line 1
               | 
               | b) a.out writes line 2
               | 
               | c) head reads line 1
               | 
               | d) head closes the pipe
               | 
               | We know that b and c both happen after a, and that d
               | happens after c. However, we do not know whether b
               | happens before c, between c and d, or after d. Your a.out
               | process will only get killed by SIGPIPE if it happens
               | after d.
               | 
               | On my system, running a.out under strace is enough to
               | slow it down enough to affect the timing and see the
               | SIGPIPE you were expecting. You may alternatively insert
               | artificial delays in your test program such as by calling
               | the sleep() function between the two lines of output to
               | see the same result.
        
               | dataflow wrote:
               | Sorry, I think I edited my comment while you were
               | replying. But I just noticed the problem in the most
               | recent version was that I didn't write to stdout after
               | the usleep(), so it never raised SIGPIPE. Thanks.
        
               | jstanley wrote:
               | > it's not plain C behavior.
               | 
               | But pipes aren't a C thing in the first place. "unistd.h"
               | is not a C thing, file descriptors aren't a C thing.
        
               | dataflow wrote:
               | The C program would just be using stdio to interact with
               | pipes.
               | 
               | And not every platform with pipes supports SIGPIPE with
               | such behavior.
        
         | hnlmorg wrote:
         | I don't personally agree with that judgement. While the failure
         | condition is at the OS level it's still affecting the function
         | of the program an in unexpected way.
         | 
         | Plus the whole point of STDOUT is that it is a file. So it
         | shouldn't change the developers mental model if that file
         | happens to be a pseudo TTY, a pipe or a traditional persistent
         | file system object. This flexibility is one of the core
         | principles of UNIX and it's what makes the POSIX command line
         | as flexible as it is.
        
           | contradictioned wrote:
           | With that in mind, is this criticism of the Java hello world
           | valid? Its output abstracts more than stdout and maybe in
           | Windows this but would not occur. (I don't know, just
           | discussing)
        
         | CraigJPerry wrote:
         | In this scenario you didn't write any bytes though. You made a
         | call to write to standard out (your process's file handle 1)
         | and didn't succeed, you didn't handle the possible error
         | condition, you just silently ignored it.
         | 
         | I think this is pretty cut and dried - the failure is inside
         | your process's address space and the programmer error is that
         | you haven't handled a reported error.
         | 
         | >> what happens to the bytes AFTER the pipe?
         | 
         | There isn't a pipe involved here, when your process was created
         | it's stdout was connected to dev/full then your program began
         | executing
        
           | usrbinbash wrote:
           | > you didn't handle the possible error condition, you just
           | silently ignored it.
           | 
           | Problem is, the error condition is not even that obvious. I
           | tried it, and printf() will happily return the number of
           | bytes written, even when redirecting stdout to /dev/full.
           | 
           | I am not 100% sure, but I think this has to do with the fact
           | that printf uses buffered io, and writing the bytes to the
           | buffer will work. It's only when the buffer is flushed that
           | this will become a problem, but this would need to be handled
           | in the code to show an error message.
        
       | fouronnes3 wrote:
       | Curious if Zig's "software should be perfect" suffers from this.
        
         | boloust wrote:
         | It does not.
        
           | kbd wrote:
           | Indeed https://news.ycombinator.com/item?id=30611768
        
       | bradwood wrote:
       | Here's another one:
       | 
       | 10 PRINT "Hell world"
        
       | [deleted]
        
       | hiccuphippo wrote:
       | This is one of the reasons zig doesn't have a print function and
       | you have to deal with the error when using the stdout writer[0].
       | 
       | [0] https://zig.news/kristoff/where-is-print-in-zig-57e9
        
         | abainbridge wrote:
         | Came here to say the same. Zig gets it right.
         | https://ziglang.org/documentation/master/#Hello-World
        
       | tgv wrote:
       | It's a fun take, but a hyperbole nonetheless. hello.c is supposed
       | to be run from a terminal and write back to it: there's always
       | space to write. It's not meant to be part of a shell script, so
       | the error status is irrelevant.
       | 
       | It does show that we take such examples a bit too literally: our
       | feeble minds don't consider what's missing, until it's too late.
       | That's a didactic problem. It only matters to certain kinds of
       | software, and when we teach many people to program, most of them
       | won't go beyond a few small programs. But perhaps the "second
       | programming course" should focus a bit less on OOP and introduce
       | error handling.
        
         | hnlmorg wrote:
         | It depends on whether you want your Hello World programs to
         | reflect an actual program or just be an approximation.
         | 
         | I'd argue there is little benefit in the latter. Particularly
         | these days where the Hello World of most imperative languages
         | look vaguely similar. Maybe back when LISP, FORTRAN and ALGOL
         | were common it was more useful showing a representation of the
         | kind of syntax one should expect. But that isn't the case any
         | more.
         | 
         | Plus given the risk of bugs becoming production issues or,
         | worse, security vulnerabilities and the ease and prevalence of
         | which developers now copy and paste code, I think there is now
         | a greater responsibility for examples to make fewer
         | assumptions. Even if that example is just Hello World.
        
           | dmurray wrote:
           | > It depends on whether you want your Hello World programs to
           | reflect an actual program or just be an approximation. I'd
           | argue there is little benefit in the latter.
           | 
           | There's a _huge_ benefit in having a program that verifies
           | you have set up the programming environment successfully and
           | can build and execute your programs. Far more than the
           | didactic benefit of any  "Hello World" program.
           | 
           | Handling terminal output is just an extra nice-to-have at
           | that point, and one convenient way to verify your tools are
           | working. Correct error handling is definitely out of scope.
        
             | hnlmorg wrote:
             | Interesting take but I see two problems with that:
             | 
             | 1. if you're testing your development environment then
             | handling errors appropriately is even more important. The
             | last thing you want to find out is that your development
             | environment doesn't work because of some edge case that
             | wasn't tested.
             | 
             | 2. if your code is just to test the development environment
             | then ship that test code with the development environment
             | rather than publish it on your home page as a practical
             | example of your languages code.
             | 
             | What you're describing is effectively a behavioral test,
             | not a Hello World example.
        
         | [deleted]
        
         | knorker wrote:
         | > a hyperbole
         | 
         | It's not, though.
         | 
         | This helloworld is not safe to use as part of something bigger.
         | Like:                   echo header > gen.txt && ./helloworld
         | >> gen.txt && ./upload_to_prod gen.txt
         | 
         | That will upload a partial file to prod, if there's any write
         | error.
         | 
         | > It's not meant to be part of a shell script
         | 
         | You don't know that. And brittle pieces like this is absolutely
         | not an uncommon source of bugs.
        
           | tgv wrote:
           | > You don't know that
           | 
           | The first piece of C code in introduction section was meant
           | as production software? I've checked it: that section
           | mentions typing "a.out" in the the UNIX shell to see what
           | happens.
        
             | knorker wrote:
             | Sorry, I'm not following. Introduction section of what?
        
               | tgv wrote:
               | The C programming language by Kernighan and Ritchie, the
               | very source of hello world.
        
           | moring wrote:
           | If you don't know how the program is going to be used, how do
           | you know that it is doing the right thing? Is "Hello world"
           | actually the expected output?
           | 
           | Also, what makes the status code handling special compared
           | to, say, - assuming the english language is the preferred
           | language instead of asking the OS for the user's preference -
           | assuming that the output has to be ASCII (or compatible)
           | instead of something like UTF-16
           | 
           | There seems to be a weird obsession with the program's status
           | code over anything else in this whole comment section, and it
           | seems to me that the only reason for that is that back in the
           | stone age of computing, the status code was the only thing
           | that got standardized, while locale and encoding didn't, so
           | properly supporting the latter is hard and therefore assumed
           | to be less important.
        
             | knorker wrote:
             | > Is "Hello world" actually the expected output?
             | 
             | Isn't that the definition of helloworld?
             | 
             | > Also, what makes the status code handling special
             | compared to, say, - assuming the english language is the
             | preferred language instead of asking the OS for the user's
             | preference - assuming that the output has to be ASCII (or
             | compatible) instead of something like UTF-16
             | 
             | True. But by any definition printing nothing is a failure.
             | If failing to print anything isn't a failure, what is?
             | 
             | > There seems to be a weird obsession with the program's
             | status code over anything else in this whole comment
             | section
             | 
             | It's because it's the only structured way to indicate
             | success. And "not printing" is clearly a failure of "print
             | 'hello world'".
             | 
             | > supporting the latter is hard and therefore assumed to be
             | less important.
             | 
             | No, but printing nothing is clearly a failure. Printing the
             | wrong language is not obviously a failure of helloworld,
             | and absolutely not something helloworld can know on its
             | own.
             | 
             | helloworld can know it failed to print what it intended to
             | print. It cannot confirm that its intentions are correct,
             | even in principle.
             | 
             | It cannot know whether some Lennartware has decided that
             | anything built before yesterday (e.g. LANG envs and
             | friends) can be ignored, and that in Lennart land all
             | programs should write a request for the language to the
             | blockchain, and wait for a reply transaction before
             | printing anything.
             | 
             | Just because you can come up with examples of errors
             | helloworld cannot check, doesn't mean that it should not do
             | error handling for the things that it can.
             | 
             | `tar` cannot check that the user actually intended the
             | particular file format flavor that it implements, but it
             | can know that failing to write it should cause it to report
             | an error.
        
       | HellsMaddy wrote:
       | You all joke that this doesn't happen in practice, but something
       | like this literally just bit me and it took me a few too many
       | minutes to figure out what was going on.
       | 
       | I use a bash script as my BROWSER which calls another bash script
       | to launch or communicate with my browser that I run inside a
       | container. The script that my BROWSER script calls has some debug
       | output that it prints to stderr.
       | 
       | I use mutt as my email client and urlscan [0] to open URLs inside
       | emails. Urlscan looks at my BROWSER environment variable and thus
       | calls my script to open whatever URL I target. Some time
       | recently, the urlscan author decided to improve the UX by hiding
       | stderr so that it wouldn't pollute the view, and so attempted to
       | pipe it to `/dev/null`. I guess their original code to do this
       | wasn't quite correct and it ended up closing the child processes'
       | stderr.*
       | 
       | I generally use `set -e` (errexit) because I want my scripts to
       | fail if any command fails (I consider that after an unhandled
       | failure the script's behavior is undefined, some other people
       | disagree and say you should never use `set -e` outside of
       | development, but I digress). My BROWSER scripts are no exception.
       | 
       | While my scripts handle non-zero returns for most things that can
       | go wrong, I never considered that writing log messages to stdout
       | or stderr might fail. But it did, which caused the script to die
       | before it was able to launch my browser. For a few weeks I wasn't
       | able to use urlscan to open links. I was too lazy to figure out
       | what was wrong, and when I did it took me a while because I
       | looked into every possibility _except_ this one.
       | 
       | Luckily this wasn't a production app. But I know now it could
       | just as feasibly happen in production, too.
       | 
       | I opened an issue[1] and it was fixed very quickly. I love open
       | source!
       | 
       | *No disrespect to urlscan, it's an awesome tool and bugs happen
       | to all of us!
       | 
       | [0]: https://github.com/firecat53/urlscan
       | 
       | [1]: https://github.com/firecat53/urlscan/issues/122
        
         | xelxebar wrote:
         | Interesting bug you found!
         | 
         | It sounds our sensibilities are similar regarding cli and tool
         | usage. This is a side note, but as someone who used to use
         | "Bash strict mode" in all my scripts, I'm now a bit bearish on
         | `set -e`, mainly due to the subtle caveats. If you're
         | interested, the link below has a nice (and long) list of
         | potentially surprising errexit gotchas:
         | 
         | https://mywiki.wooledge.org/BashFAQ/105
         | 
         | (The list begins below the anecdote.)
        
         | underdeserver wrote:
         | > I use a bash script as my BROWSER which calls another bash
         | script to launch or communicate with my browser that I run
         | inside a container.
         | 
         | I'm not sure return codes are the source of your troubles...
        
         | necrotic_comp wrote:
         | I'm actually really curious about this setup. Can you go into a
         | bit more detail about how it works ?
        
           | HellsMaddy wrote:
           | I run Firefox inside a systemd-nspawn[0] container. I wrote a
           | little wrapper around systemd-nspawn that I call arch-lwc[1]
           | which kinda mimics the docker CLI. I have another script to
           | coordinate the Firefox-specific stuff.
           | 
           | [0]: https://wiki.archlinux.org/title/Systemd-nspawn
           | 
           | [1]: https://github.com/b0o/arch-lwc
        
         | shikoba wrote:
         | > some other people disagree and say you should never use `set
         | -e` outside of development
         | 
         | I'm really interested. What are their arguments? And how do
         | they handle errors?
        
           | bombcar wrote:
           | See https://mywiki.wooledge.org/BashFAQ/105 for examples of
           | side effects.
           | 
           | I think the idea is you use set -e during development to find
           | where you should catch errors, but in production you may want
           | it off to reduce strange side-effects (or explicitly check
           | for success in the way you expect; so not that the command
           | returned 0 but that the file it made exists and is the right
           | length, etc).
        
             | deathanatos wrote:
             | > _but in production you may want it off to reduce strange
             | side-effects_
             | 
             | Having -e set _is_ to reduce strange side-effects, by
             | having the script fail, instead of plowing headlong into
             | the land of undefined /unexpected behavior.
             | 
             | > _Seehttps://mywiki.wooledge.org/BashFAQ/105 for examples
             | of side effects._
             | 
             | The `if` bit _should_ be well-known if you 're writing
             | bash. The pipe bit is unfortunate, and is why -o pipefail
             | is recommended, too. Or, just writing in a sane language
             | that isn't going to page you in the middle of the night.
        
             | tedunangst wrote:
             | That seems like a really weak argument. Sometimes set -e
             | won't catch an error, therefore it's better to let all
             | errors slip through? "You're supposed to handle every
             | error." Yeah, okay, set -e doesn't interfere with that.
        
             | shikoba wrote:
             | > not that the command returned 0 but that the file it made
             | exists and is the right length
             | 
             | If a command returns 0 when it didn't really do its job.
             | Shouldn't we fix the command instead of the script?
        
               | bombcar wrote:
               | See the link for examples of _bash and posix_ internals
               | that don't necessarily operate the way you'd expect.
        
       | hwinked wrote:
        
       | karolist wrote:
       | The author missed another bug, which many others do. You need a
       | comma after Hello, as in "Hello, World!" because it's a direct
       | address. Very, and I mean VERY few books get this right.
       | 
       | 0. https://www.grammar-
       | monster.com/lessons/commas_with_vocative...
        
         | dylan604 wrote:
         | Nah, it's an oxford comma.
        
           | karolist wrote:
           | Being a non-native speaker I had to look it up. Wikipedia
           | says it's optional in British English but American English
           | encourage, and sometimes mandate the use of it. If you look
           | at https://commons.wikimedia.org/wiki/File:Hello_World_Brian_
           | Ke..., which the author uses in their post - the comma's
           | there. So why the inconsistency?
        
             | dylan604 wrote:
             | Yeah, I wasn't really serious seeing as the Oxford comma
             | applies to a list of items. It's something that has never
             | set well with me, the Oxford commma, as it makes the last
             | item ambiguous when not present. So I now just throw out
             | "Oxford comma" anytime there's a question on if a comma is
             | needed or not.
        
       | andi999 wrote:
       | It would be more interesting if the post shows how to detect that
       | error. (and how the other language examples look, at least on
       | mobile I dont see them)
        
       | [deleted]
        
       | steerablesafe wrote:
       | You can't have bugs if you don't have a specification. [insert
       | meme here]
        
       | prewett wrote:
       | /dev/full is brilliant, I need to remember that!
       | 
       | On the other hand, it's kind of depressing that I can't even
       | write to stdout without needing to check for errors. And what are
       | you going to do if that fails? Write to stderr? What if that
       | fails because the program was run with `2>&1` ?
        
       | enriquto wrote:
       | Ignoring the return of printf is a "bug". For the hello-world
       | example, you can simply pass the printf value to main: "return
       | printf(...) > 0;"
        
         | jwilk wrote:
         | That's insufficient, because printf() is buffered.
        
         | dataflow wrote:
         | It's not necessarily an error to print less than you intended
         | though. The consumer might have simply decided that they didn't
         | need the rest of the input. Whether or not it's an error
         | depends on _why_ the write failed to occur. Usually out-of-
         | space is an error, whereas pipe-is-closed /has-reached-EOF is
         | not.
        
       | unixbane wrote:
        
       | Ericson2314 wrote:
       | This is good. All the people questioning the spec need to realize
       | is handling the error should be opt- _out_ , not opt-in.
       | 
       | #[must_use] in Rust is the right idea: Rust doesn't automatically
       | do anything --- there is no policy foisted upon the programmer
       | --- but it will reliably force the programmer to do _something_
       | about the error explicitly.
        
       | pron wrote:
       | To those perplexed by the behaviour of Java's Hello World, as
       | Java is otherwise very careful with error handling, this is
       | because System.out is a java.io.PrintStream, and that's its
       | documented behaviour:[1]
       | 
       | > Unlike other output streams, a PrintStream never throws an
       | IOException; instead, exceptional situations merely set an
       | internal flag that can be tested via the checkError method.
       | 
       | So the correct Hello World would be:
       | System.out.println("Hello World!");         if
       | (System.out.checkError()) throw new IOException();
       | 
       | While the behaviour of PrintStream cannot be changed (it goes
       | back to Java 1.0, and I'm guessing that the intention was not to
       | require handling exceptions when writing messages to the standard
       | output), adding a method to obtain the underlying, unwrapped
       | OutputStream might be an idea worth considering, as it would
       | allow writing to the standard output just like to any file.
       | 
       | [1]:
       | https://docs.oracle.com/en/java/javase/17/docs/api/java.base...
        
         | barrkel wrote:
         | It's a behavioural side-effect of checked exceptions. Because
         | IOException is a checked exception, throwing it for console
         | output would cause a lot of pain for printf debugging.
        
           | winstonewert wrote:
           | This interesting me because it shows that they had experience
           | really early on in Java development that checked exceptions
           | caused pain, and rather than realize that something was wrong
           | with the design they started swallowing and ignore errors.
        
           | pron wrote:
           | Well, Java has since introduced RuntimeIOException, which
           | could be used in cases where an IO exception is unexpected,
           | so we could introduce a new class, say,
           | ErrorCheckingPrintStream, and add the method
           | `ErrorCheckingPrintStream withErrorChecks()` to PrintStream
           | if it's considered sufficiently worthwhile. So you could
           | have:
           | System.out.withErrorChecks().println("Hello World!);
           | 
           | But we can't change the behaviour of the existing
           | PrintStream.
        
             | barrkel wrote:
             | I think you mean UncheckedIOException - https://docs.oracle
             | .com/javase/8/docs/api/java/io/UncheckedI...
             | 
             | RuntimeIOException from Jira has an amusing (and incorrect,
             | IMO) message:
             | 
             | https://docs.atlassian.com/software/jira/docs/api/7.6.1/com
             | /...                   > An IOException was encountered and
             | the stupid programmer didn't know          > how to
             | recover, so this got thrown instead.
             | 
             | It's a misguided doc comment because "recovering" from
             | error is usually the wrong thing to do - usually the right
             | thing is to abort whatever action is taking place, whether
             | it's a request handler, event loop or standalone program.
             | Situations like low disk space, incorrect file permissions,
             | missing files and so on usually can't be recovered deep in
             | the stack or without manual intervention.
        
       | ale42 wrote:
       | I was expecting Free Pascal not to have the bug, as Pascal
       | generally fails with a visible runtime error, as it does I/O
       | checking by default. However, it seems not to do it when WriteLn
       | goes to the standard output... (even if it is then piped to
       | /dev/full). So the basic                   begin
       | WriteLn('Hello World!');         end.
       | 
       | definitely has the bug, at least with the fpc implementation. On
       | the other hand, explicitly trying to write to /dev/full from the
       | Pascal source triggers a beautiful message:
       | Runtime error 101 at $0000000000401104
        
       | lifeisstillgood wrote:
       | This is (IMHO) the difference between unit testing and other
       | testing.
       | 
       | Unit testing verifies it does what it is supposed to do _ideally_
       | , and all other tests verify it can do it in non-ideal
       | environments.
        
       | yesenadam wrote:
       | I couldn't see GNU Hello mentioned in the article or comments so
       | far. I wonder how it fares bug-wise.
       | 
       |  _The GNU Hello program produces a familiar, friendly greeting.
       | Yes, this is another implementation of the classic program that
       | prints "Hello, world!" when you run it._
       | 
       |  _However, unlike the minimal version often seen, GNU Hello
       | processes its argument list to modify its behavior, supports
       | greetings in many languages, and so on. The primary purpose of
       | GNU Hello is to demonstrate how to write other programs that do
       | these things; it serves as a model for GNU coding standards and
       | GNU maintainer practices._
       | 
       | https://www.gnu.org/software/hello/
        
         | mcbrit wrote:
         | This is explicitly called out and handled in lines 151-155.
         | 
         | https://git.savannah.gnu.org/cgit/hello.git/tree/src/hello.c
         | 
         | Here's the comment:                 /* Even exiting has
         | subtleties.  On exit, if any writes failed, change          the
         | exit status.  The /dev/full device on GNU/Linux can be used for
         | testing; for instance, hello >/dev/full should exit
         | unsuccessfully.          This is implemented in the Gnulib
         | module "closeout".  */
        
           | pixelbeat__ wrote:
           | Jim Meyering's discussion on how this is handled usually in
           | GNU programs
           | 
           | https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-
           | goodb...
        
       | parker78 wrote:
       | It's only a bug if the requirements are:
       | 
       | "Print Hello World and indicate if it succeed or not"
       | 
       | If the requirements were:
       | 
       | "Print Hello World, then return 0"
       | 
       | It's working as intended.
       | 
       | I'd even go so far as to say that print(); return 0; should
       | always return 0, it would be weird for such a program to ever
       | return anything other than 0 (where would that return come
       | from?).
        
         | pjerem wrote:
         | The requirement of a "Hello World" program is always, by
         | nature, to print "Hello World".
         | 
         | If my program calls your Hello World program, it expects it to
         | print Hello World. That's basically the point of the program.
         | 
         | If your program don't print Hello World for whatever reason, of
         | course you don't need to manage the error if it wasn't
         | specified. But it's probably a bad thing (call it a bug or not)
         | to exit 0 which the caller will interpret by "Hello World have
         | just been printed successfully", I can go on and print ",
         | John".
         | 
         | I agree it's probably not going to be in the requirements, and
         | world will probably not collapse if you don't manage the error,
         | but it's with no doubt an idiom required by most OSes to ensure
         | programs are normally working.
         | 
         | You can also create orphan processes if it's needed by your
         | requirements, but it's probably a bug or a hole in your
         | requirements. Because at some point, non idiomatic programs
         | will be used in situations where they will be creating issues.
         | And we are talking about issues that are very hard to even
         | spot.
         | 
         | Those "non requirements" are exactly how you lately discover
         | that you have no logs from the last two weeks or that your
         | backups aren't complete.
         | 
         | It's not requirement, but it's just hygiene.
         | 
         | tbf, I'm arguing of what should be an idea world, but I
         | probably have myself written those sorts of bugs. Writing
         | idiomatic code is hard and no one is to blame for not doing it
         | perfectly. I just think it's some ideal to aim for.
        
           | cowl wrote:
           | But you are forgetting where to print. The classic "Hello
           | World" program requires you to Print to a terminal not to a
           | file. The fact that the terminal and a file can be used
           | interchangeably in *nix system is not the responsibility of
           | the program. Likewise, Printing "hello world" to my 3d
           | printer is not the purpose either. My "hello world" program
           | is meant to Assure the minimum possible feedback that the
           | toolchain is working as expected and being called by your
           | program is not a part of that purpose.
        
           | usrbinbash wrote:
           | > That's basically the point of the program.
           | 
           | The point of hello.c is to serve as demonstration to students
           | of the language of what a very basic program looks like, and
           | how to use the toolchain to get it to run.
           | 
           | That's it, that's the requirements specified.
        
         | hgomersall wrote:
         | In your pseudocode, "Print Hello World" doesn't come with any
         | caveats, like "unless there is an error, in which case silently
         | don't print Hello World". If an error might occur, your
         | description is incomplete if you don't describe the policy that
         | should be taken.
         | 
         | Your second point might be fine, except that it doesn't
         | describe the API that languages actually use to print. For
         | sure, it's trivial to implement the policy you describe, but
         | suggesting that everyone always needs that policy is rather
         | limiting and makes light of the real bugs that failure to
         | handle errors actually results in.
        
         | dmurray wrote:
         | The interesting case for me is if the requirements were "print
         | Hello World". I'd argue that the one with an explicit return
         | value is incorrect in that case, because the extra line of code
         | leads you to believe an extra requirement exists which is to
         | indicate success.
        
       | [deleted]
        
       | RcouF1uZ4gsC wrote:
       | What happens if in the original hello world example, you
       | unplugged the monitor?
       | 
       | Would any of the languages report an error?
       | 
       | Maybe they all have bugs.
        
       | 8n4vidtmkvmk wrote:
       | ok but how does one fix this bug in c or c++?
        
         | steerablesafe wrote:
         | `puts` has a return value indicating success or failure.
         | 
         | edit: https://cigix.me/c17#7.21.7.9.p3
        
       | pixelbeat__ wrote:
       | Jim Meyering's classic "Goodbye World" talk on this
       | 
       | https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodb...
        
       | hwinked wrote:
        
       | albertzeyer wrote:
       | In the end, it states that the language C has the bug. But this
       | is wrong. In C, there are no exceptions, i.e. all error checking
       | has to be explicit. This is just the language. So when you now
       | ignore the error, this is not a bug of the language but just a
       | bug in your code. The only thing you could argue is that this is
       | a bad language design.
       | 
       | Or maybe this about global stdout object. With buffering enabled
       | (by default), printf will not throw any error. The fflush would
       | do. But a final fflush would be done implicitly at the end. But
       | this is again all well documented, so still, this is not really a
       | bug but maybe just bad language design.
       | 
       | I'm not exactly sure what C++ code was used. If this was just the
       | same C code, then the same thing applies. And iostream just
       | behaves exactly as documented.
        
       | bestouff wrote:
       | Well that's precisely the mindset of C/C++. You have to think by
       | yourself about everything that can go wrong with your code. And,
       | man, lots of things can go wrong.
       | 
       | I find more modern languages so much less exhausting to use to
       | write correct code.
        
         | shikoba wrote:
         | In C, yes. But for C++ you can use exceptions if you want. You
         | just have to "protect" all the "primitives" and after that
         | you're safe.
        
         | onion2k wrote:
         | _I find more modern languages so much less exhausting to use to
         | write correct code._
         | 
         | Modern languages do catch more programmer errors than C/C++,
         | but the more general point is that there are "edge cases"
         | (redirecting to a file isn't an edge case) that developers need
         | to consider that _aren 't_ magically caught, and understanding
         | the language you use well enough so as not to write those bugs
         | is important.
         | 
         | The more experience I get as a dev the more I've come to
         | understand that building the functionality required in a
         | feature is actually a very small part of the job. The "happy
         | path" where things go right is often trivial to code. The
         | complexity and effort lies in making sure things don't break
         | when the code is used in a way I didn't anticipate. Essentially
         | experience means anticipating more ways things can go wrong.
         | This article is a good example of that.
        
           | masklinn wrote:
           | > Modern languages do catch more programmer errors than
           | C/C++, but the more general point is that there are "edge
           | cases" (redirecting to a file isn't an edge case) that
           | developers need to consider that aren't magically caught, and
           | understanding the language you use well enough so as not to
           | write those bugs is important.
           | 
           | But GP's point is that modern languages _can_ surface those
           | issues and edge cases, and try to behave somewhat sensibly,
           | but even sometimes "magically" report the edge cases in
           | question.
           | 
           | That's one of the things which is very enjoyable (though
           | sometimes frustrating) in Rust, the APIs were (mostly)
           | designed such that you _must_ acknowledge all possible errors
           | somehow, either handling it or explicitly suppressing it.
        
             | tialaramex wrote:
             | Indeed. One of the things you notice when writing say,
             | Advent of Code solutions in Rust is that you're writing
             | unwrap() a lot e.g. something like
             | 
             | let geese = usize::from_str_radix(line.strip_prefix("and
             | also ").unwrap().strip_suffix(" geese.").unwrap(),
             | 10).unwrap();
             | 
             | All these functions, usize::from_str_radix,
             | str::strip_prefix, str::strip_suffix are Options which
             | could be None, and we need to decide what to do with that
             | option, Option::unwrap() says I promise this is Some(thing)
             | and I want that thing. If you're wrong and the Option was
             | None, Rust will panic.
             | 
             | Sure enough sometimes while rushing to solve an AoC problem
             | you may get told your program panicked because you tried to
             | unwrap None. Oops, I wrote "geese" above and thus didn't
             | handle the scenario where it might say "goose" in the input
             | when there was only one of them... need to handle that
             | properly.
             | 
             | In a C program taking the same approach it's easy to skip
             | that unwrap() step. After all we're sure it's fine...
             | aren't we? And in the absence of any error reporting now
             | all you know is AoC says the answer you got was wrong and
             | you've got no idea what the problem is.
        
               | EuAndreh wrote:
               | > Sure enough sometimes while rushing to solve an AoC
               | problem (...)
               | 
               | >In a C program taking the same approach (...)
               | 
               | "Writing C" and "rushing" is a strategy that cannot give
               | you correct, robust software.
        
           | jll29 wrote:
           | Exactly. And isn't UNIX all about writing tools that can be
           | used in unintended ways and combinations?
        
         | kazinator wrote:
         | In this case, I don't see how they help.
         | 
         | A modern language could automatically throw an exception if the
         | string cannot be completely written to standard output.
         | 
         | But that has not necessarily helped. The program now has a
         | surprising hidden behavior; it has a way of terminating with a
         | failed status that is not immediately obvious.
         | 
         | If it is used in a script, that could bite someone.
         | 
         | In Unix, there is such an exception mechanism for disconnected
         | pipes: the SIGPIPE error. That can be a nuisance and gets
         | disabled in some programs.
        
         | Sohcahtoa82 wrote:
         | C/C++ basically do only exactly what you tell them and nothing
         | more, which is why they're so much faster than other languages.
         | 
         | There's no garbage collection/reference counting/etc. going on
         | in the background. Objects aren't going to be moved around
         | unless you explicitly move them around (Enjoy your heap
         | fragmentation!). In C, you don't even get exceptions.
         | 
         | Of course, this creates TONS of foot-guns. Buffer overflows,
         | unchecked errors, memory leaks, etc. A modern language won't
         | have these, except for memory leaks, but they're much less
         | likely to happen in trivial to moderate complexity apps.
        
         | jcelerier wrote:
         | Considering that awk and TCL do not have the bug in question,
         | while java, ruby, and node.js do, I'm not sure this can be
         | framed in terms of modernity
        
           | pdw wrote:
           | Well, I'd say that the older languages mostly get it right,
           | while the newer languages mostly fail.
        
             | ketzu wrote:
             | I am not sure either follows. But it depends how we even
             | define "older languages", especially considering
             | differences between python 3 and 2, are they the same age
             | (based on the original python release) or are they treated
             | for their respective release version?
             | 
             | Just taking some simple release dates [1] or wikipedia I
             | found:
             | 
             | Ages of "Yes" group: 49, 36, 22, 26, 26, 12, 31
             | 
             | Ages of "No" group: 11, 14, 33, 7, 32, 45, 26, 34, 21
             | 
             | Averages: Yes 28.85, No 24.78
             | 
             | With the ambiguity around what "age" even means for the
             | language here (e.g., counting the age of node.js or python)
             | it is probably meaningless, but it seems well mixed
             | independent of age.
             | 
             | [1] https://blog.sunfishcode.online/bugs-in-hello-world/
        
               | ketzu wrote:
               | Aparently I copy-pasted the wrong link:
               | 
               | [1] https://iq.opengenus.org/age-of-programming-
               | languages/
        
           | josefx wrote:
           | I just realized I never thought about why System.out.println
           | doesn't declare an IOException. Turns out PrintStream
           | silently catches the exception and turns it into an error
           | flag no one ever checks. Undermining both the Ability to
           | handle IO errors using exceptions and making it impossible to
           | find out what happened over what I assume was the ability to
           | call System.out.println without checking for errors. Right
           | now I am just happy that my IO code generally writes to
           | binary streams so I don't have to rush through my code base
           | to check for that nasty surprise.
        
         | em3rgent0rdr wrote:
         | And the mindset can be to treat such conditions as "Don't Care"
         | to allow the C/C++ code to generate more optimized assembly.
        
           | josefx wrote:
           | You could have a way to explicitly flag that, instead C will
           | assume that any accidentally silently discarded result can be
           | optimized around with and kick of a landslide of changes that
           | turns bugs ten times worse.
        
         | jollybean wrote:
         | Yes. First you learn to 'code stuff' and as your experience
         | progresses in that language you merely learn more and more of
         | the ways it can be wrong, and then you worry more, and have to
         | overthink every little thing.
         | 
         | The hidden costs are enormous and to this day still not very
         | well accounted for.
        
       | joosters wrote:
       | Since the article is being pedantic, here's another pedantic
       | complaint: What if printf() can't write _all_ of its output, but
       | manages to write some of it? printf() returns the number of bytes
       | written, but (and I 'm sure someone will correct me if I'm
       | wrong!) it doesn't guarantee to be atomic - it can't either write
       | everything or nothing. Imagine a complicated printf() call with
       | lots of parameters and long strings - some of it might get
       | written, then the next write() that it does fails due to lack of
       | space. What does printf() do then?
       | 
       | The article cites an example of writing a YAML file and the
       | dangers of it being half-written. Well, you could imagine
       | outputting a file all in one printf() with lots of %s's in the
       | format string. Some get written, but not all. If printf() decides
       | to return an error message, retrying the printf() later on (after
       | deleting another file, say), will corrupt the data because you'll
       | be duplicating some of the output. But if printf() just returned
       | the number of bytes written, your program will silently miss the
       | error.
       | 
       | So does 'Hello World\n' need to check that printf() succeeded, or
       | does it actually need to go further and check that printf()
       | returned 12? (or is it 13, for \r\n ?) I don't think there's any
       | way to really safely use the function in real life.
        
         | shikoba wrote:
         | If printf can write some bytes but not all of them. The C
         | documentation is explicit:
         | 
         | > a negative value if an output error occurred
         | 
         | So in your case that's an error and printf returns a negative
         | value. But yes, how many bytes were written is a lost
         | information.
        
         | enriquto wrote:
         | > So does 'Hello World\n' need to check that printf()
         | succeeded, or does it actually need to go further and check
         | that printf() returned 12?
         | 
         | No. According to fprintf(1), when the call succeeds it returns
         | the number of printed characters. If it fails (for example, if
         | it could only print part of the string) then it returns a
         | negative value.
         | 
         | The number of printed characters is useful to know how much
         | space was used on the output file, not to check for success.
         | Success is indicated by a non-negative return value.
        
       | dwohnitmok wrote:
       | This raises an interesting question: is there any IO function
       | that should return unit/void? Or equivalently are there any IO
       | functions for which we can safely ignore the return value/ignore
       | all exceptions?
       | 
       | It seems like every single IO thing I can think of can have a
       | relevant error, regardless of whether it's file-system related,
       | network, or anything else.
        
         | dataflow wrote:
         | I think you can certainly return void, and you can ignore any
         | I/O exceptions up to the top layer of the stack, but then you
         | have to decide whether the exception should result in an error
         | code to the user or not. Some (like "out of disk space") are
         | usually errors, whereas others (like "no more data" or "pipe is
         | closed") may not be.
        
         | andreyv wrote:
         | In C, and many other languages, the file stream error state is
         | saved after each operation, so you can skip error checking on
         | every output line and only do                 if
         | (fflush(stdout) != 0 || ferror(stdout) != 0)       {
         | perror("stdout");         return EXIT_FAILURE;       }
         | 
         | at the end of the program. The same should be done for stderr
         | as well.
         | 
         | In GNU programs you can use atexit(close_stdout) to do this
         | automatically.
        
           | PennRobotics wrote:
           | Would perror() return the first/oldest error or the last?
        
             | andreyv wrote:
             | Right -- ferror() does not set errno, and so perror() is
             | not appropriate here. fprintf(stderr, ...) would be better.
        
           | Asooka wrote:
           | I wish this post were higher up, since it shows the idiomatic
           | way to deal with that problem, unlike the article. Obviously
           | the designers of the Unix i/o interface thought about this
           | and provided for a simple way of handling it.
        
       | thedatamonger wrote:
       | Bravo! I enjoyed this.
        
       | avar wrote:
       | A real world example of catching (some, but certainly not all)
       | fflush(), ferror() etc. cases is what "git" does at the end of
       | its execution, the first highlighted line is where it's returning
       | from whatever function implements a built-in ("status", "pull",
       | "log" etc. etc.):
       | :https://github.com/git/git/blob/v2.35.0/git.c#L464-L483
       | 
       | Doing something similar would be a good addition to any non-
       | trivial C program that emits output on stdout and stderr.
       | 
       | In practice I haven't really seen a reason to exhaustively check
       | every write to stdout/stderr as long as standard IO is used, and
       | fflush() etc. is checked.
       | 
       | A much more common pitfall is when dealing with file I/O and
       | forgetting to check the return value of close(). In my experience
       | it's the most common case where code that _tries_ to get it wrong
       | actually gets it wrong, I 've even seen code that checked the
       | return value of open(), write() and fsync(), but forgot about the
       | return value of close() before that fsync(). A close() will fail
       | e.g. if the disk is full.
        
         | Anthony-G wrote:
         | I work as a sysadmin and only write the odd program/script
         | (Python, Perl, Bash). In the past, I've run into the problem of
         | not being able to write to a log file (disk full or
         | insufficient permissions) so I now check for these situations
         | when opening or writing to a file.
         | 
         | A while ago, I started learning C in my personal time and am
         | curious about this issue. If `close()` fails, I'm guessing
         | there's not much else the program can do - other than print a
         | message to inform the user (as in the highlighted git code).
         | Also, I would have thought that calling `fsync()` on a file
         | descriptor would also return an error status if the
         | filesystem/block device is full.
        
           | avar wrote:
           | For both close() and fsync() it depends on how they fail. You
           | should generally call them in a loop and retry as long as
           | they're returning an error that's EINTR. I.e. to retry past
           | signal interruptions.
           | 
           | This is really more about POSIX and FS semantics than C
           | (although ultimately you end up using the C ABI or kernel
           | system calls, which are closer to C than e.g. Python).
           | 
           | POSIX gives implementations enough leeway to have close() and
           | fsync() do pretty much whatever they want as far as who
           | returns what error goes, as long as not returning an error
           | means your data made it to storage.
           | 
           | But in practice close() is typically 1=1 mapped to the file
           | itself, while fsync() is many=1 (even though both take a
           | "fd"). I.e. many implementations (including the common
           | consumer OS's like Windows, OSX & Linux) have some notion of
           | unrelated outstanding I/O calls being "flushed" by the first
           | process to call fsync().
           | 
           | IIRC on ext3 fsync() was pretty much equivalent to sync(),
           | i.e. it would sync all outstanding I/O writes. I believe that
           | at least Windows and OSX have a notion of doing something
           | similar, but for all outstanding writes to a "leaf" on the
           | filesystem, i.e. an fsync() to a file in a directory will
           | sync all outstanding I/O in that directory implicitly.
           | 
           | Of course none of that is anything you can rely on under
           | POSIX, where you not only have to fsync() each and every file
           | you write, but must not forget to also flush the relevant
           | directory metadata too.
           | 
           | All of which is to say that you might be out of space when
           | close() happens, but by the time you'd fsync() you may no
           | longer be out of space, consider a write filling up the disk
           | and something that frees up data on disk happening
           | concurrently.
           | 
           | If you know your OS and FS semantics you can often get huge
           | speedups by leaning into more lazily syncing data to disk,
           | which depending on your program may be safe, e.g. you write
           | 100 files, fsync() the last one, and know the OS/FS syncs the
           | other 99 implicitly.
           | 
           | But none of that is portable, and you might start losing data
           | on another OS or FS. The only thing that's portable is
           | exhaustively checking errors after every system call, and
           | acting appropriately.
        
       | pickledcods wrote:
       | Do not forget the notes on dup2(). It's about the automatic
       | closing of newfd before it gets replaced. I've bumped into this
       | situation several times, that is why I'm mentioning it.
       | SYNOPSIS        int dup2(int oldfd, int newfd);            NOTES:
       | If newfd was open, any errors that would have been reported at
       | close(2) time are lost.       If this is of concern, then the
       | correct approach is not to close newfd before calling dup2(),
       | because of the race condition described above.       Instead,
       | code something like the following could be used:             /*
       | Obtain a duplicate of 'newfd' that can subsequently           be
       | used to check for close() errors; an EBADF error           means
       | that 'newfd' was not open. */             tmpfd = dup(newfd);
       | if (tmpfd == -1 && errno != EBADF) {            /* Handle
       | unexpected dup() error */        }             /* Atomically
       | duplicate 'oldfd' on 'newfd' */             if (dup2(oldfd,
       | newfd) == -1) {            /* Handle dup2() error */        }
       | /* Now check for close() errors on the file originally
       | referred to by 'newfd' */              if (tmpfd != -1) {
       | if (close(tmpfd) == -1) {                /* Handle errors from
       | close */            }        }
        
       | Reventlov wrote:
       | And to catch such a thing in C, if anyone was wondering, you
       | would have to fflush stdout.
        
         | sunfish wrote:
         | This is true, however if we modify the program to print a
         | 4096-byte long string instead of just the "hello world" string,
         | then it's not sufficient again. And of course, the number 4096
         | is system-dependent.
         | 
         | So to _really_ do hello world in C right, in addition to
         | fflush, you also need to check the return value from puts. I
         | 've never seen any C tutorial do that though.
        
           | jll29 wrote:
           | #include <stdio.h>
           | 
           | int main(void) { return !(puts("Hello, new world!") == EOF);
           | /* return 0 unless a rare I/O error occurs */ }
        
             | nemetroid wrote:
             | First off, main should return 0 on success, not 1. Second,
             | puts() will happily return 0 when writing to /dev/full.
        
               | kikoreis wrote:
               | Only if the write is buffered, right?
        
           | moring wrote:
           | What would be the expected reaction if either puts or fflush
           | returns an error code? You might think, write a message on
           | stderr (which may be different from stdout), but what if
           | stderr is also redirected to a full device? How would you
           | react to the error code returned from that?
           | 
           | To me this is an indication that you need to know the context
           | in which the program gets run, and its purpose in that
           | context. Or you'd have to specify every edge case, but I've
           | never seen that really work in practice.
        
             | throwawaylinux wrote:
             | > What would be the expected reaction if either puts or
             | fflush returns an error code? You might think, write a
             | message on stderr (which may be different from stdout), but
             | what if stderr is also redirected to a full device? How
             | would you react to the error code returned from that?
             | 
             | I don't think you would react any differently on stderr
             | failure unless you had a more complex system with alternate
             | ways to report such things configured.
             | 
             | Just ignore it and continue to report the major error
             | (stdout) in your exit status.
        
           | wahern wrote:
           | Because errors on FILE streams are persisted until clearerr,
           | it should be sufficient to check the return value of fflush
           | at the end of the program. Presumably the FILE I/O interface
           | was deliberately designed this way, so error checking could
           | be consolidated at the end of a series of operations.
        
       | s_ariga wrote:
       | #include <stdio.h> #include <stdlib.h>
       | 
       | int main(void) { if(puts("Hello, World!")!=EOF) { return
       | EXIT_SUCCESS; }else { return EXIT_FAILURE; } }
        
         | jwilk wrote:
         | This still succeeds, because puts() is buffered.
         | 
         | (And silently returning non-zero would be bad anyway.)
        
         | Karellen wrote:
         | puts(), like printf() and all the C-standardised "stdio"
         | functions use buffered writes. So that is also buggy, because
         | the buffer won't be flushed until after main() returns. You
         | need to call and check the return value of "fflush(stdout)"
         | manually to get the correct result.
        
         | PennRobotics wrote:
         | puts() returns 13 with no pipe and with pipe to /dev/full,
         | which I just learned is due to buffering.
         | 
         | What worked for me initially was the POSIX write() function:
         | #include <stdlib.h>       #include <unistd.h>              int
         | main(void)       {           int status;           status =
         | write(1, "Hello World!\n", 13);           if (status < 0)  {
         | return EXIT_FAILURE; }           return EXIT_SUCCESS;       }
         | 
         | -----
         | 
         | As someone else commented, fflush() gives the desired error
         | response.                 #include <stdio.h>       #include
         | <stdlib.h>              int main(void)       {           int
         | status;           puts("Hello World!");           status =
         | fflush(stdout);           if (status < 0)  { return
         | EXIT_FAILURE; }           return EXIT_SUCCESS;       }
         | 
         | -----
         | 
         |  _andreyv_ probably has the best alternative[1], which is
         | checking fflush() and ferror() at the program 's end and
         | calling perror(). It's better because it outputs an actual
         | error message on the current terminal, and you don't need to
         | write a special error checking wrapper.
         | 
         | [1] https://news.ycombinator.com/item?id=30611924
        
         | unwind wrote:
         | I tried basically exactly that, and it didn't work for me.
         | 
         | On my test system (Ubuntu 21.10 on x86_64) the puts() call
         | never fails.
         | 
         | I switched to a raw write() and that successfully catches it,
         | by returning -1 when output is redirected to /dev/full.
         | 
         | Quite interesting, actually.
        
       | usrbinbash wrote:
       | Enjoyable read for sure, but i think the question whether ot not
       | this constitutes a bug or not is open for interpretation.
       | 
       | IMHO, it doesn't.
       | 
       | hello.c is written in a way that makes it very clear that the
       | program doesn't care about error conditions in any way shape or
       | form; the return value of printf is ignored, the output isn't
       | flushed, the return of flushing isn't checked, noone looks at
       | errno; ...so anything happening that could go wrong will go
       | unreported, unless its something the OS can see (segfault,
       | permission, etc.)
       | 
       | If I expect a program to do something (eg. handle IO errors) that
       | its code says very clearly that it doesn't, that's not the
       | programs fault.
        
         | l33t2328 wrote:
         | You shouldn't have to read the code to understand what a
         | program does, from an external POV at least.
        
           | usrbinbash wrote:
           | If the program comes with a specification then yes, ideally
           | that should be the case.
           | 
           | If there isn't one however, then the code is all there is.
        
         | rplnt wrote:
         | So the question boils down to: Is hello world a program that is
         | supposed to write hello world or is it a program that is
         | supposed to (compile and) start? For me it's usually the
         | latter.
        
           | shikoba wrote:
           | None. Hello world is a program for beginners to teach them
           | the most basic way to debug a program. The sole purpose of
           | hello world is to explain how to print internal state of a
           | program. In that case the examples are correct, they succeed.
        
             | bombcar wrote:
             | Exactly. When you're done with hello world you've solved
             | some major problems:
             | 
             | 1. How to store the code in a file
             | 
             | 2. How to find and use the compiler and linker
             | 
             | 3. How to run compiled code
        
               | shikoba wrote:
               | 4. How to find if a function terminates
        
             | ryandrake wrote:
             | I think a lot of people are getting too hung up on the fact
             | that the application is specifically "hello world." The
             | code is clearly buggy, even if you don't particularly care
             | about bugs in this specific application. The author would
             | have been better off not mentioning "hello world" as the
             | application and simply said:
             | 
             | Find the bug!                   puts("This is a log");
             | 
             | I think everyone can agree that the above should be
             | considered a bug in any kind of non-hello-world production
             | code, for the reason the article mentioned.
        
             | hombre_fatal wrote:
             | Of course, it's also a way to show even experienced devs
             | the most basic boilerplate to begin working from in
             | something new.
             | 
             | What are the starting incantations and how do I run it.
        
         | stonemetal12 wrote:
         | >If I expect a program to do something (eg. handle IO errors)
         | that its code says very clearly that it doesn't, that's not the
         | programs fault.
         | 
         | Is there no such thing as a bug then? The program does what the
         | code says so every "misbehavior" and crash is expected
         | behavior.
        
           | parksy wrote:
           | If we're going to reductio ad absurdum then OS's shouldn't
           | let programs run if they interact with a device and don't
           | explicitly handle exceptions for those devices. The OS is
           | just a program and if it allows programs to crash it, isn't
           | that the OS's fault?
        
           | usrbinbash wrote:
           | There is a difference between a program with a
           | specification/documentation outlining what it should do, and
           | one that doesn't have a spec.
           | 
           | AFAIK, hello.c doesn't have a spec, so the code _is_ the
           | spec. If I am using it, I have to read the code to know what
           | it does.
        
             | admax88qqq wrote:
             | So if I don't write a spec, I have no bugs. Got it.
        
               | ericHosick wrote:
               | This Porsche has a design flaw: I can only go 22,500
               | kilometers before I have to replace the tires.
               | 
               | This VW bus has a design flaw: I can't pull more than 1G
               | on the race track.
        
         | oneeyedpigeon wrote:
         | This is a strange definition of 'buggy' to me. Surely it
         | shouldn't depend on anything to do with the source code,
         | otherwise closed-source programs are all 'neither-buggy-nor-
         | not-buggy' and that can't be the case...
        
           | User23 wrote:
           | Most software is incapable of being incorrect, because
           | correct behavior isn't defined! Hence the questions here
           | about who's to say what's correct. When the typical
           | programmer says a program is "buggy" it means "it didn't do
           | what I want." That's a pleasantness property, not a
           | correctness one.
        
             | myrrlyn wrote:
             | given that the sole purpose of software is to do what
             | people want, "it didn't do what i want" is automatically
             | incorrect behavior
        
               | monkpit wrote:
               | So that would mean as soon as your software has 2 users,
               | it is broken because it doesn't conform to their 2
               | (different) imaginary specs?
        
               | thfuran wrote:
               | Yes, unless you have a sufficiently complex and
               | inscrutable EULA to point to as proof that at least one
               | of the users was out of spec.
        
           | [deleted]
        
         | layer8 wrote:
         | For didactic reasons it's preferable to consider it a bug.
        
           | usrbinbash wrote:
           | hello.c is meant as the first program a new student
           | encounters when learning C. At that point, the student has
           | enough to worry about; writing code to a file, checking for
           | syntactic errors, basic program structure, using the
           | compiler, executing the binary, understanding what `#include
           | <stdio.h>` means,...
           | 
           | Sure, we could update it:                   // hello_v2.0.c
           | #include <stdio.h>         #include <string.h>
           | #include <errno.h>              int main(void) {
           | printf("Hello, World!\n");             fflush(stdout);
           | if (errno != 0) {                 fprintf(stderr, "error:
           | %s\n", strerror(errno));                 return errno;
           | }         }
           | 
           | But now we have different libraries, a multitude of external
           | identifiers, control structures, blocks, return values, the
           | concept of buffered streams, the concept of file-descriptors,
           | the printf formatting language, program return values,
           | boolean logic & conditionals,...
           | 
           | To someone who is already experienced in another language,
           | that may not seem like a big deal, and isn't, but to someone
           | who encounters the language for the first time, this is heavy
           | stuff.
        
             | paavoova wrote:
             | Your usage of errno can cause failure on success if errno
             | was set any time before the call to fflush. Might need a
             | bump to v3 for robustness...
        
               | gpm wrote:
               | According to cppreference
               | 
               | > The value of errno is 0 at program startup, and
               | although library functions are allowed to write positive
               | integers to errno whether or not an error occurred,
               | library functions never store 0 in errno.
               | 
               | https://en.cppreference.com/w/c/error/errno
               | 
               | So this program correctly tests whether printf _or_
               | fflush wrote to errno. You just can 't refactor it into a
               | function you call not at startup... I suppose that is
               | unless you're keeping to a convention where you always
               | set errno back to 0 after any call that might have set
               | it...
        
             | layer8 wrote:
             | This is much simpler:                 int n =
             | printf("Hello, World!\n");       return n < 0 ?
             | EXIT_FAILURE : EXIT_SUCCESS;
        
               | usrbinbash wrote:
               | On my machine:                   $ cat main.c
               | #include <stdio.h>              int main(void) {
               | int n = printf("Hello, World!\n");             return n <
               | 0 ? 1 : 0;         }              $ cc main.c         $
               | a.out         Hello, World!         $ echo $?         0
               | $ a.out > /dev/full         $ echo $?         0
               | 
               | Problem is, printf is buffered. When redirecting to
               | /dev/full writing to the buffer works. The problem only
               | manifests when the buffer is flushed.
               | 
               | This works, as write is not buffered;
               | #include <unistd.h>              int main(void) {
               | int n = write(1, "Hello, World!\n", 14);
               | return n < 0 ? 1 : 0;         }
               | 
               | But even then, to find out what _exactly_ went wrong, we
               | would have to check errno.
               | 
               | It's easier in Go, because fmt functions do have an error
               | return which will report such errors;
               | func main() {             _, err := fmt.Println("Hello,
               | World!")             if err != nil {
               | os.Exit(1)             }         }
        
               | layer8 wrote:
               | I was expecting that printf would auto-flush at the
               | newline, but either it doesn't, or doesn't report an
               | error? An explicit fflush would probably work.
               | 
               | In any case, hello world examples are used for two
               | purposes, (a) providing a simplest possible program that
               | you can run (with an indication that it worked), and (b)
               | to give a taste of the respective programming language.
               | At least for the latter case, it would be useful to
               | demonstrate the necessary error handling, if any. The
               | above is just showing that this is not entirely trivial
               | in C with its standard library.
        
               | Erwin wrote:
               | The file objects in the library can operate as
               | unbuffered, line-buffered or fully (i.e. block) buffered.
               | When your stdout is connected to a terminal, it is line-
               | buffered and flushes on newline. But when you redirect to
               | a file, it becomes block buffered.
        
       | moltenguardian wrote:
       | Even Golang de facto suffers from this. I don't think I can name
       | a time I saw someone check the return value of fmt.Print or
       | log.Print. Not checking the return value still seems the the
       | "right" thing to do.
        
         | silisili wrote:
         | This seems to be checking return values, which is a very unixy
         | thing to do.
         | 
         | Most Go in the wild is doing way more than a typical *nix
         | binary, so the use case differs.
         | 
         | If you want a resilient system, you don't die on print and log
         | failures.
        
           | rini17 wrote:
           | Usually the result of ignoring "disk full" or partial writes
           | is not resilience but byzantine failure instead.
        
         | Tobu wrote:
         | Indeed, first result and no error checking:
         | 
         | https://gobyexample.com/hello-world
        
         | Thaxll wrote:
         | Because fmt and log print are not usually not used for this, if
         | I were to do it properly I would use io.Copy() which everyone
         | check the result.
         | 
         | Checking the result of log and print is very tedious and not
         | useful most of the time.
        
         | hnlmorg wrote:
         | I do. But then I'm writing a shell (like Bash/Fish/etc but more
         | DevOps focused) so if I don't handle all types of errors then
         | the entire UX falls apart.
        
       | inopinatus wrote:
       | tldr: always check the return value of system calls.
       | 
       | (also, printf is buffered, so close or flush your output)
        
       | asojfdowgh wrote:
       | And not an edge case in the slightest either.
       | 
       | I'm going to have to go back over all the print statements I've
       | ever written now
        
         | unixbane wrote:
         | Just wait til DAY OF THE SEAL.
        
       | kaycebasques wrote:
       | The article doesn't explain how to fix the bug! Talk about
       | leaving your audience hanging.
        
       | paradite wrote:
       | Since we are going pedantic here, here are 3 bugs that I found in
       | the blogpost:
       | 
       | 1. Node.js result is out-dated. I run on Node.js v14.15.1 hello
       | world code below on macOS and it reported exit code 1 correctly:
       | // testlog.js       console.log('hello world')
       | process.exit(0)            // bash       $ node -v       v14.15.1
       | $ node testlog.js > /dev/full       -bash: /dev/full: Operation
       | not permitted       $ echo $?       1
       | 
       | 2. Node.js is not a language. JavaScript is a language, and
       | Node.js is a JavaScript runtime environment that runs on the V8
       | engine and executes JavaScript code outside a web browser.
       | 
       | 3. Missing JavaScript result in the table, which is the most
       | popular language on GitHub: https://octoverse.github.com/#top-
       | languages-over-the-years
        
         | dmurray wrote:
         | > 2. Node.js is not a language. JavaScript is a language,
         | 
         | This criticism is the wrong way around. All of the author's
         | "languages" are actually _language implementations_ like
         | NodeJS. You can tell because he produced the results by running
         | the code, rather than by reading a spec.
        
           | paradite wrote:
           | Yes you are right about the differences.
           | 
           | So what I'm proposing is to put JavaScript in the language
           | column (like other languages such as Java) and note the usage
           | of Node.js as the implementation in the second column
           | together with version (similar to Java -> openjdk 11.0.11
           | 2021-04-20).
           | 
           | Would that make sense?
        
             | dmurray wrote:
             | That would certainly make sense, but the author hasn't
             | followed that pattern consistently so far - e.g. the row
             | for C doesn't list the compiler or architecture.
        
         | 0x0 wrote:
         | Since macOS does not have /dev/full, I think what is actually
         | happening here is your bash shell fails to create a file named
         | "full" in "/dev" and so the bash shell exits with an error;
         | this has nothing to do with node.js.
        
           | gwd wrote:
           | Yeah -- that's bash that's reporting the error; it looks like
           | node never actually gets run.
        
           | paradite wrote:
           | Yeah you are probably right.
           | 
           | Now I'm curious about another interesting question. Should
           | bash be the one that handle the error and exit code in this
           | case? Since it seems to be responsible for handing the piping
           | operation.
        
             | moonchild wrote:
             | If by 'in this case' you mean the problem mentioned by the
             | linked article, then no. The shell is not 'responsible for
             | handling the piping operation'. It creates the pipe, but is
             | not responsible for moving data through it.
        
               | paradite wrote:
               | Makes sense. Thanks for the explanation!
        
         | ksbrooksjr wrote:
         | On node 16.9 console.log doesn't produce a non-zero exit code,
         | but process.stdout.write does, and gives me a decent error
         | message as well:                 internal/fs/utils.js:332
         | throw err;           ^       Error: ENOSPC: no space left on
         | device, write           at writeSync (fs.js:736:3)
        
           | terinjokes wrote:
           | This is because console.log isn't the equivalent to the
           | post's printf. It is purposefully opaque to the application
           | (and applications should not assume anything happens with the
           | input).[0]
           | 
           | > Its main output is the implementation-defined side effect
           | of printing the result to the console.
           | 
           | [0]: https://console.spec.whatwg.org/#logger
        
         | jwilk wrote:
         | It wouldn't make sense to include JavaScript, because it
         | doesn't have the concept of stdout.
        
       | Sporktacular wrote:
       | Still confused. It seems some people think there is nothing to
       | fix, some think the programmer needs to act to prevent it, some
       | think ANSI and other creators of the affected languages would
       | need to act to prevent it.
       | 
       | If we accept the idea that the function (non-coding use of the
       | word) of an language's indication of success should - indicate
       | success (or its absence) - of a piece of code, then surely the
       | creators of the languages should make it do just that. That's
       | their job right no? What am I missing?
        
       | xlii wrote:
       | I'm disappointed. I expected some obscure edgecase (like "Main is
       | usually a function..." [1]) but instead that's about scope
       | handling, contract design and responsibility shift.
       | 
       | "Hello world" method simply calls an API to a text interface. It
       | uses simple call, to a simple interface that is expected to be
       | ever present. I don't find any bug there. It won't work if such
       | interface isn't available, is blocked or doesn't exist. It won't
       | work on my coffee grinder nor on my screwdriver. It won't work on
       | my Arduino because there is no text interface neither.
       | 
       | Of course, one could argue that user might expect you to handle
       | that error. That's all about contracts and expectation. How
       | should I deal with that? Is the "Hello world" message such
       | important that the highest escalated scenario should be painted
       | on the sky? I can imagine an awkward social game where we throw
       | each other obscure challenges and call it a bug.
       | 
       | It's nitpicking that even such simple code might fail and I get
       | it. It will also fail on OOM, faulty hardware or if number of the
       | processes on the machine hit the limit. Maybe some joker replaced
       | bindings and it went straight to 3D printer which is out of
       | material? _My expectations_ were higher based on the title.
       | 
       | Now allow me to excuse myself, I need to write an e-mail to my
       | keyboard manufacturer because it seems like it has a bug which
       | prevents it from working when slightly covered in liquid coffee.
       | 
       | [1]: http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-
       | not-...
        
         | pdw wrote:
         | I found it interesting. If you generalize a bit, the question
         | is "Will a naively written stdio program handle IO errors?".
         | The fact that for several popular languages the answer is "no"
         | is disappointing.
        
           | Beltiras wrote:
           | To me it was less disappointing and more intriguing.
        
         | LadyCailin wrote:
         | Sounds like we need to use https://github.com/Hello-World-
         | EE/Java-Hello-World-Enterpris... to cover all our bases.
        
         | [deleted]
        
         | nebulous1 wrote:
         | My initial take was the same as yours. However, I would be of
         | the opinion that the program would definitely be _better_ if it
         | returned non-zero on failure, so the question for me is whether
         | it rises to the level of  "bug" or not. In retrospect I can't
         | think of when I wouldn't consider a program silently failing to
         | not be a bug (unless specifically designed to silently fail),
         | so I've come round to agreement with the article.
        
         | matheusmoreira wrote:
         | I also had higher expectations after reading the title and was
         | disappointed when I realized it was about failure to handle all
         | possible system call results. I thought it was gonna be a bug
         | in the C standard library or something.
         | 
         | I still agree with the author though. This _is_ a serious
         | matter and it seems most of the time the vast amount of
         | complexity that exists in seemingly simple functionality is
         | ignored.
         | 
         | Hello world is not "simply" calling a text interface API. It is
         | asking the operating system to write data somewhere. I/O is
         | exactly where "simple" programs meet the real world where
         | useful things happen and it's also where things often get ugly.
         | 
         | Here's all the stuff people need to think about in order to
         | handle the many possible results of a single write system call
         | on Linux:                 long result = write(1, "Hello",
         | sizeof("Hello") - 1);            switch (result) {         case
         | -EAGAIN:           /* Occurs only if opened with O_NONBLOCK. */
         | break;         case -EWOULDBLOCK:           /* Occurs only if
         | opened with O_NONBLOCK. */           break;         case
         | -EBADF:                  /* File descriptor is invalid or
         | wasn't opened for writing. */           break;         case
         | -EDQUOT:           /* User's disk quota reached. */
         | break;         case -EFAULT:           /* Buffer points outside
         | accessible address space. */           break;         case
         | -EFBIG:           /* Maximum file size reached. */
         | break;         case -EINTR:           /* Write interrupted by
         | signal before writing. */           break;         case
         | -EINVAL:           /* File descriptor unsuitable for writing.
         | */           break;         case -EIO:           /* General
         | output error. */           break;         case -ENOSPC:
         | /* No space available on device. */           break;
         | case -EPERM:           /* File seal prevented the file from
         | being written. */           break;         case -EPIPE:
         | /* The pipe or socket being written to was closed. */
         | break;       }
         | 
         | Some of these are unlikely. Some of these are irrelevant. Some
         | of these are _very_ important. Virtually all of them seem to be
         | routinely ignored, especially in text APIs.
        
           | Perseids wrote:
           | And specifically _no space left on device_ is a very common
           | error that is also very commonly handled badly. Happened to
           | me yesterday and the error messages I got were unhelpful or
           | non-existent. In Firefox part of a website I was desperately
           | trying to use just stopped reacting for _some_ functionality.
           | Developer tools opened as a blank space. Importing a calendar
           | entry in Evolution produced an inscrutable SQLite error.
           | Starting Chromium (as backup browser in the hopes that the
           | website would work better there) via Gnome did not open any
           | window or show any error. It was only when I tried to start
           | Chromium via the console that I saw a helpful error message
           | for the first time.
           | 
           | Also I always start to mildly panic in such cases, as lots of
           | software corrupts its on-disk state more when the hard drive
           | is full than any segfault, OOM-kill or hard shutdown is able
           | to. I can understand and empathize on how this happens from a
           | software development perspective, but objectively speaking
           | "our entire field is bad at what we do, and if you rely on
           | us, everybody will die". ( https://xkcd.com/2030/ )
        
           | dfox wrote:
           | Userspace should not expect that any given syscall can only
           | return some set of known errno values. You should enumerate
           | the cases where you want to do some kind of special handling
           | (with EINTR being somewhat more important that other cases)
           | and have path to somehow handle even unexpected errno values.
           | 
           | Both Linux man pages and SUS specify some set of possible
           | error situations, but not all of them. In the man pages case
           | the set is not at all fixed and is subject to change and
           | often does not contain some of the more obscure error states.
           | The SUS "Errors" section are explicitly not meant to be
           | complete and the OS can return additional errno values,
           | additionally the OS can even handle some of the error cases
           | as undefined behavior and not return any error code at all
           | (notable example: doing anything to already joined pthread_t
           | on linux, whish is undefined and does not return -ESRCH).
        
             | matheusmoreira wrote:
             | You're right. The manual contains this ominous notice at
             | the very end of the errors section:
             | 
             | https://man7.org/linux/man-pages/man2/write.2.html
             | 
             | > Other errors may occur, depending on the object connected
             | to fd.
             | 
             | I don't understand why every possible result isn't
             | explicitly documented. This is the Linux system call
             | interface, we need to know everything that could happen
             | when we make these calls.
        
               | dfox wrote:
               | The right assumption is that every syscall can return any
               | defined errno value. In practice this means that you
               | should handle the cases that you have to handle (-EINTR
               | and for write(2) incomplete writes, which are typical
               | reason for "fatal error: Success"), that you can somehow
               | handle (things like retries for -ENOSPC) and log
               | strerror(3) result for anything that you don't expect
               | (whether you shoult then abort(), exit() or continue
               | depends on how critical the failed syscall was).
        
         | boloust wrote:
         | The bug is not that the program failed, it's that the program
         | failed but reported a success.
        
           | nojs wrote:
           | I think they are arguing that it didn't fail, it did
           | everything you asked of it (it didn't claim to successfully
           | print hello world in every scenario, just to attempt to write
           | to the buffer you gave it, which it did).
        
             | boloust wrote:
             | No, I think the argument was pretty clearly that "the
             | program failed but it can't possibly be expected to work
             | under every conceivable scenario".
        
             | geocar wrote:
             | It seems possible they are arguing:
             | _exit(write(1,"Hello World!\n",13));
             | 
             | is the correct one, but to me that just kicks the can. What
             | should happen here?                   os.rename(x,y)
             | print("success!")
             | 
             | should this exit nonzero? The file _did_ get renamed and
             | progress was made, even if some unrelated problem occurs,
             | so some animation for a users benefit who is probably
             | dealing with some other problems thinking piping to
             | /dev/full was a good idea in the first place, well, it just
             | seems almost _cruel_ to further burden them with a
             | surprising error code, so maybe I should wrap that print
             | line in a pokemon since the output doesn 't really matter
             | that much anyway.
             | 
             | So it is I prefer to think of bugs as the difference
             | between expectation and reality, and I think it should be
             | fair to say different users can be predisposed to have
             | different expectations; So I also I think it matters a
             | great deal what the contract/expectations are.
             | 
             | But I also know the difference between /dev/full and
             | /dev/null
        
             | pickledcods wrote:
             | The program failed to check the return value of the called
             | function.
        
             | bmacho wrote:
             | Well, if you want your hello world program to try to write
             | Hello world, then report success regardless of the result,
             | then it is bug-free. If you intend your program to write
             | hello world on your screen/stdout, then it is definitely
             | buggy.
             | 
             | The computer will do what you ask it to do, it's only a
             | bug, when it doesn't meet your expectations.
        
           | dotancohen wrote:
           | To quote one of my favorite books:                 > [Hello,
           | world] is the big hurdle. To leap over it you have to be able
           | to       > create the program text somewhere, compile it
           | successfully, load it, run       > it, and find out where
           | your output went.
           | 
           | Those are the goals of "Hello, world!". Create the program,
           | compile it, load it, run it, and find the output. Things that
           | are not goals of "Hello, world!" are handling user input,
           | reusable components (functions), network access, etc etc etc,
           | error handling.
           | 
           | It's fine that the error is not handles, just as it is fine
           | that the output went to stdout. Error handling was not a goal
           | of the program.
        
             | tialaramex wrote:
             | > you have to be able to create the program text somewhere,
             | compile it successfully, load it, run it, and find out
             | where your output went.
             | 
             | Note that Rust "cheats" for you here, if you ask Cargo to
             | make you a new Rust project then by default the project it
             | gives you will perform Hello, World correctly when you
             | "cargo run". It will also be version controlled (if Cargo
             | can't figure out what type of version control you prefer,
             | but git is installed, you get a git repo).
             | 
             | Rust's Hello World also of course panics if given a full
             | output device. Because just ignoring errors by default,
             | while very C, is not a good idea and in Rust it's much
             | easier to respond to unexpected errors by just panicking
             | rather than ignoring them.
        
             | hnlmorg wrote:
             | > Those are the goals of "Hello, world!". Create the
             | program, compile it, load it, run it, _and find the
             | output_.
             | 
             | (Emphasis mine)
             | 
             | How are you going to find the output if there is an error
             | outputting it and you're not capturing that?
             | 
             | Given the requirements you've given, that would absolutely
             | make error handling mandatory in my opinion.
        
               | dotancohen wrote:
               | Interesting perspective, with which I happen to disagree.
               | But I appreciate considering it, thank you.
        
             | oneeyedpigeon wrote:
             | I guess the argument is that the non-error-checking version
             | fails at the "find out where your output went" stage.
             | hello.c gives the impression that your output went to the
             | file, even when it didn't.
             | 
             | Without a spec, I think it would be harsh to claim hello.c
             | is _wrong_. But handling the error--in this case, returning
             | it from main to the shell via an exit code--is definitely
             | _more correct_.
        
           | usrbinbash wrote:
           | Whether the program fails or not, is a matter of
           | specification.                   printf("Hello, World!\n")
           | 
           | Is me saying: "Do a write syscall to stdout. I don't care
           | what the return value is, I don't care if the flush is
           | successfull if stdout happens to be buffered." If that is
           | what I want to do, aka. what the program is specified to do,
           | then it didn't fail.
        
             | ryandrake wrote:
             | To me, your example says: "I forgot about the return
             | value." The way I learned C, if you really, really want to
             | say "I don't care what the return value is" you'd
             | explicitly cast it, nicely documenting your active non-
             | caring about the return value for future code readers:
             | (void) printf("Hello, World!\n");
             | 
             | Although, in general, ignoring the return value from things
             | like puts() and printf() is a bad idea, for reasons the
             | article makes clear.
        
           | KnobbleMcKnees wrote:
           | Failure isn't defined by programmed control structures, it's
           | defined by requirements and implemented via programming.
           | 
           | If the requirements of a hello world program include
           | accounting for all error boundaries of the host system, then
           | I am yet to see them written down but would invite anyone to
           | provide them.
           | 
           | The parent comment has made a start in this regard.
        
             | yawboakye wrote:
             | every program is given the 3 stdio channels: stdin, stdout,
             | stderr. if the program is unable to use any of these as
             | expected, it's an error that should be reported back to the
             | user. today it's /dev/full but tomorrow it could be a log
             | file with wrong access perms. you don't want to be
             | returning 2 weeks later to find out that nothing was
             | written, and your program didn't complain.
        
               | secondcoming wrote:
               | Agreed, the log file example is quite good, but also
               | something like a systemd script reporting success when it
               | actually failed.
        
               | ChrisSD wrote:
               | This is true of *nix but on Windows they can be absent.
               | In face this is the default for GUI applications.
        
             | Cthulhu_ wrote:
             | This particular category of bug is something people - those
             | making up the requirements too - probably wouldn't even
             | consider.
        
               | KnobbleMcKnees wrote:
               | Agreed. In a real world business requirements scenario,
               | it would be dropped as premature optimisation if it was
               | even considered at all.
        
             | ptsneves wrote:
             | And this is the reason unit tests are very often
             | insufficient and provide vanishingly small value. They test
             | programming details, while integration tests test
             | requirements implemented via programming. Loved your first
             | paragraph.
        
         | yawboakye wrote:
         | stdio is program input, and a program's user should be informed
         | about bad inputs. that said, usually, where hello world is
         | usually demonstrated is far away from i/o so perhaps the
         | negligence. but to argue that it's behaving correctly here is
         | unnecessary.
        
       | shultays wrote:
       | Should "hello world" return error if it actually prints something
       | but there was no person to read the output? Maybe the user was
       | distracted and was not looking at the screen. Does a "hello
       | world" program make sound if no one hears it?
       | 
       | Sounds like the program failed its objective, greeting the world.
       | And thus imho shouldn't return 0.
        
         | masklinn wrote:
         | The program's objective was to output the information, not to
         | ensure that a user read it. Redirecting to `/dev/null` is a
         | valid and common use of a program warranting no warning, so is
         | running a program, collecting its log, then ultimately
         | discarding it having never looked at it (in fact it's the norm
         | of well-behaved and solid programs).
        
       | bandrami wrote:
       | This is interesting because it's not clear "who owns" that error,
       | the program itself or the shell that sets up the redirection.
        
         | b-zee wrote:
         | Definitely thought-provoking. A few responses here on HN
         | disagree with calling this a bug, so maybe the user owns the
         | error. This is all related to what kind of contract we have in
         | mind when creating and using such a program.
         | 
         | If `puts` were to be used for debug messages, it might be right
         | not to fail so as to not disturb the rest of the program. If
         | the primary purpose is to greet the world, then we might expect
         | it to signal the failure. But each creator or user might have
         | their own expected behaviors.
         | 
         | If a user expects different behavior, then perhaps it is a
         | feature request:
         | 
         | > There's no difference between a bug and a feature request
         | from the user's perspective.
         | (https://blog.codinghorror.com/thats-not-a-bug-its-a-
         | feature-...)
         | 
         | The question is how the behavior can be made more explicit. I
         | think it's a reasonable default to make programs fail often and
         | early. If some failure can be safely ignored, it can always be
         | implemented as an (explicit) feature.
        
         | heleninboodler wrote:
         | I think it's clearly main() that "owns" that error, since it's
         | the one that swallowed it. It would be impossible for the shell
         | to own it since it's impossible for the shell to even detect
         | it, given this program's buggy behavior.
         | 
         | I find the argument that the code obviously ignores the error
         | so that's obviously the program's intent to be completely
         | spurious. The code "obviously" intends to print the string,
         | too, and yet in some cases, it doesn't actually do that. It's
         | clearly a bug. I don't think it's particularly _useful_ to harp
         | on this bug in the most introductory program ever, but it 's
         | definitely a bug.
        
       | pretzelhands wrote:
       | Seems like PHP does something reasonably right for once!
       | $ php hello.php > /dev/full         $ echo $?         255
       | 
       | It doesn't exactly print an error, but at least it returns
       | something non-zero.
        
         | oneeyedpigeon wrote:
         | Which version are you using? Because mine gives a zero return
         | code. I'm running 7.0.33.
        
       | darkerside wrote:
       | TIL about /dev/full
       | 
       | Could have used this knowledge in the past...
        
         | incanus77 wrote:
         | I've been using Linux for 24 years and didn't know about this.
         | Mind blown.
        
       ___________________________________________________________________
       (page generated 2022-03-09 23:01 UTC)