10 ms·
Bugs in Hello World
- lifeisstillgood 5y agoThis 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.
- hiccuphippo 5y agoThis 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 https://zig.news/kristoff/where-is-print-in-zig-57e9
- abainbridge 5y agoCame here to say the same. Zig gets it right. https://ziglang.org/documentation/master/#Hello-World https://ziglang.org/documentation/master/#Hello-World
- asojfdowgh 5y agoAnd 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 5y agoJust wait til DAY OF THE SEAL.
- shultays 5y agoShould "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 5y agoThe 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).
- avar 5y agoA 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 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 5y agoI 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 5y agoFor 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.
- pron 5y agoTo 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/java/io/PrintStream.html https://docs.oracle.com/en/java/javase/17/docs/api/java.base...
- barrkel 5y agoIt'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.
- pron 5y agoWell, 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 5y agoI think you mean UncheckedIOException - https://docs.oracle.com/javase/8/docs/api/java/io/UncheckedIOException.html 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/atlassian/jira/util/RuntimeIOException.html 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.
- bestouff 5y agoWell 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.
- jcelerier 5y agoConsidering 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
- josefx 5y agoI 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.
- pdw 5y agoWell, I'd say that the older languages mostly get it right, while the newer languages mostly fail.
- ketzu 5y agoI 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/ https://blog.sunfishcode.online/bugs-in-hello-world/
- thedatamonger 5y agoBravo! I enjoyed this.
- Reventlov 5y agoAnd to catch such a thing in C, if anyone was wondering, you would have to fflush stdout.
- sunfish 5y agoThis 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.
- wahern 5y agoBecause 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.
- moring 5y agoWhat 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 5y ago> 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.
- moltenguardian 5y agoEven 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 5y agoThis 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 5y agoUsually the result of ignoring "disk full" or partial writes is not resilience but byzantine failure instead.
- hnlmorg 5y agoI 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.
- Tobu 5y agoIndeed, first result and no error checking: https://gobyexample.com/hello-world https://gobyexample.com/hello-world
- Thaxll 5y agoBecause 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.
- bor0 5y agoIt'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 5y agoYup — I found this worked well: https://www.thedroidsonroids.com/blog/dev-full-osx https://www.thedroidsonroids.com/blog/dev-full-osx
- cyborgx7 5y ago>Linux has this fun device file called "/dev/full" Yes it is. And it specifies the OS as well.
- PennRobotics 5y agoI 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--/12a6cdda22ffbd82f28ecc93e0b5bcb8 https://gist.github.com/koral--/12a6cdda22ffbd82f28ecc93e0b5...
- mccorrinall 5y agoImo 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 5y agoYour 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 5y ago> 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.
- dataflow 5y ago> 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.
- fouronnes3 5y agoCurious if Zig's "software should be perfect" suffers from this.
- tgv 5y agoIt'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 5y agoIt 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 5y ago> 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 5y agoInteresting 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.
- enriquto 5y agoIgnoring the return of printf is a "bug". For the hello-world example, you can simply pass the printf value to main: "return printf(...) > 0;"
- dataflow 5y agoIt'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.
- jwilk 5y agoThat's insufficient, because printf() is buffered.
- unixbane 5y ago
- dwohnitmok 5y agoThis 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.
- andreyv 5y agoIn 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.
- Asooka 5y agoI 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.
- PennRobotics 5y agoWould perror() return the first/oldest error or the last?
- andreyv 5y agoRight — ferror() does not set errno, and so perror() is not appropriate here. fprintf(stderr, ...) would be better.
- dataflow 5y agoI 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.
- xlii 5y agoI’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-a-function.html http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-...
- LadyCailin 5y agoSounds like we need to use https://github.com/Hello-World-EE/Java-Hello-World-Enterprise-Edition https://github.com/Hello-World-EE/Java-Hello-World-Enterpris... to cover all our bases.
- boloust 5y agoThe bug is not that the program failed, it's that the program failed but reported a success.
- nojs 5y agoI 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).
- kazinator 5y ago> 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.
- MauranKilom 5y ago> 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).
- ComradePhil 5y ago> 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.
- jjnoakes 5y ago> 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).
- 5y ago
- 8n4vidtmkvmk 5y agook but how does one fix this bug in c or c++?
- steerablesafe 5y ago`puts` has a return value indicating success or failure. edit: https://cigix.me/c17#7.21.7.9.p3 https://cigix.me/c17#7.21.7.9.p3
- albertzeyer 5y agoIn 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.
- s_ariga 5y ago#include <stdio.h> #include <stdlib.h> int main(void) { if(puts("Hello, World!")!=EOF) { return EXIT_SUCCESS; }else { return EXIT_FAILURE; } }
- Karellen 5y agoputs(), 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.
- unwind 5y agoI 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.
- jwilk 5y agoThis still succeeds, because puts() is buffered. (And silently returning non-zero would be bad anyway.)
- PennRobotics 5y agoputs() 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 https://news.ycombinator.com/item?id=30611924
- paradite 5y agoSince 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 https://octoverse.github.com/#top-languages-over-the-years
- ksbrooksjr 5y agoOn 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 5y agoThis 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 https://console.spec.whatwg.org/#logger
- 0x0 5y agoSince 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 5y agoYeah -- that's bash that's reporting the error; it looks like node never actually gets run.
- bandrami 5y agoThis is interesting because it's not clear "who owns" that error, the program itself or the shell that sets up the redirection.
- b-zee 5y agoDefinitely 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-request/ 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 5y agoI 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 5y agoSeems 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 5y agoWhich version are you using? Because mine gives a zero return code. I'm running 7.0.33.
- sedatk 5y agoDid the author just assume the spec of Hello World?
- andai 5y agoAn interesting link in the article: "Main is usually a function. So then when is it not?" https://news.ycombinator.com/item?id=27504254 https://news.ycombinator.com/item?id=27504254
- HellsMaddy 5y agoYou 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 https://github.com/firecat53/urlscan [1]: https://github.com/firecat53/urlscan/issues/122 https://github.com/firecat53/urlscan/issues/122
- underdeserver 5y ago> 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...
- xelxebar 5y agoInteresting 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 https://mywiki.wooledge.org/BashFAQ/105 (The list begins below the anecdote.)
- andi999 5y agoIt 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)
- steerablesafe 5y agoYou can't have bugs if you don't have a specification. [insert meme here]
- parker78 5y agoIt'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?).
- hgomersall 5y agoIn 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 5y agoThe 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.
- pjerem 5y agoThe 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.
- deleted 5y ago[deleted]
- joosters 5y agoSince 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 5y agoIf 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 5y ago> 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.
- inopinatus 5y agotldr: always check the return value of system calls. (also, printf is buffered, so close or flush your output)
- deleted 5y ago[deleted]
- edejong 5y agoCorrect solution: printf '#include <stdio.h>\nint main() { return printf("Hello world!\\n") && fflush(stdout); }\n' | cc -xc - && ./a.out > /dev/full && echo "Success\!"
- Someone 5y agoShouldn’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 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.
- ale42 5y agoI 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
- yesenadam 5y agoI 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/ https://www.gnu.org/software/hello/
- mcbrit 5y agoThis is explicitly called out and handled in lines 151-155. https://git.savannah.gnu.org/cgit/hello.git/tree/src/hello.c 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". */
- yesenadam 5y agoThank you.
- pixelbeat__ 5y agoJim Meyering's discussion on how this is handled usually in GNU programs https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodbye-world.pdf https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodb...
- RcouF1uZ4gsC 5y agoWhat 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.
- pixelbeat__ 5y agoJim Meyering's classic "Goodbye World" talk on this https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodbye-world.pdf https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodb...
- pickledcods 5y agoDo 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 */ } }
- usrbinbash 5y agoEnjoyable 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.
- layer8 5y agoFor didactic reasons it’s preferable to consider it a bug.
- usrbinbash 5y agohello.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.
- nextaccountic 5y agoThis just demonstrates that C is an awful programming language for writing correct programs. Compare this with Rust, where the usual hello world will just do the right thing: $ cat > a.rs fn main() { println!("Hello World"); } $ rustc a.rs $ ./a Hello World $ echo $? 0 $ ./a > /dev/full thread 'main' panicked at 'failed printing to stdout: No space left on device (os error 28)', library/std/src/io/stdio.rs:1187:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace $ echo $? 101
- deleted 5y ago[deleted]
- darkerside 5y agoTIL about /dev/full Could have used this knowledge in the past...
- incanus77 5y agoI’ve been using Linux for 24 years and didn’t know about this. Mind blown.
- kaycebasques 5y agoThe article doesn't explain how to fix the bug! Talk about leaving your audience hanging.
- Too 5y agoIt did. There was a long table of languages not having the bug at the end. ;)
- hwinked 5y agoI don’t think this is a bug. The program writes to stdout which is guaranteed by *ix to be there. If it is full, it would block until the kernel serviced it. In your examples, the user asked the shell to redirect to a full file and it reported the error.
- hwinked 5y ago
- bradwood 5y agoHere's another one: 10 PRINT "Hell world"
- Ericson2314 5y agoThis 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.
- karolist 5y agoThe 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_case.htm https://www.grammar-monster.com/lessons/commas_with_vocative...
- dylan604 5y agoNah, it's an oxford comma.
- karolist 5y agoBeing 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_Kernighan_1978.jpg 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 5y agoYeah, 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.
- Sporktacular 5y agoStill 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?
- sundarurfriend 5y agoJulia (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.
- prewett 5y ago/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` ?
- skolskoly 5y agoDoesn't seem like anyone has posted the bug free implementation so here it is: int main(void) { char the_terminal[] = "Hello World!\n" return 0; }