Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Are the %%'s in front of functions necessary? #545
I couldn't really figure out from the guide why they were needed in front of many function calls.
They look like they might be unnecessary, and they also add to the visual confusion / verbosity / complexity / lack of clarity in the language (IMO). Is there a way the compiler can automate whatever it is they are there for?
Have a look at http://ziglang.org/documentation/master/#errors
@thejoshwolfe is working on a proposal to get rid of the
There's currently a discussion going on to possibly remove the
The operator means "the function i'm calling here is declared to possibly return an error (a
There's an abundance of code in existence right now that overuses the
No, and that's a feature of the language. They are there to acknowledge the possibility of an error, and there's no default for what to do with errors when they happen. The programmer must do something with an error wherever it can happen.
This design decision is to encourage code authors to think about error cases and handle them appropriately in version 0.0.0 of their code. It's always possible to explicitly ignore errors with
Might'n't there be an even simpler solution by changing entirely how errors are handled in the language?
My intent here is to be a muse, not a rabble rouser. I'm very interested in safe, secure, performant languages, and am happy to see Zig existing and competing with the likes of Rust. I'm also interested in language design and in simplicity as a vehicle to security.
From my experience I am very cognizant of the importance of removing as much syntax from languages as possible. Less syntax = less room for errors, less to learn, and less cognitive overload, so if there's a way to rework the language error handling to just not need this frequent usage of
Well, before I could make a proposal I would have to study the language much more to figure out what purpose the operator is currently serving, and how error handling is handled in general.
Just thinking out loud here...
The docs say:
Um, so, could you design the language to just not do that? Or do it in another way if for some reason it's critical?
"panics in debug mode" sounds like some sort of exception handling mechanism.
"unwraps an error union type" sounds like a weird type system thing.
Earlier the docs say:
Like, is this syntax really necessary to cause the app to panic?
Why not just panic on unhandled errors like other languages do?
I can't really think why I would want to say that I am "absolutely sure this function will not generate an error". If I want a function to not generate an error, I will write it in such a way that it won't generate an error, and if the compiler needs to know that no error will be generated (for some reason) it can figure that out based on the implementation of the function.
It may be that '%%' is a case of slightly premature optimization. I happen to really like the idea that wrapping a type with % allows you a way to return errors without resorting to Go's multiple return value idea. The problem with Go's way is that 99% of the time, you are returning a value or an error but not both. So, you either use one of the return values or the other. The way that Zig works makes this extremely common case very, very clean.
Since that is a very common idiom and really easy to scan for when hardening code.
@taoeffect , I think you might be confusing Zig errors with exceptions. They are definitely not that. Think of it as a side channel on which you can get error values. It is almost identical in use to Go's multiple return values, but with a much more pleasing (IMO) flow to the code. Rust does something similar and it also works very nicely.
I admit to not having dived into the details of Rust's error handling either, and Go code I've only skimmed, so perhaps that's part of the issue on my end.
Still, this sentence sounds weird to me:
Why is it my business to "know with complete certainty that an expression will never be an error"? Seems to me that's clearly the compiler's area of concern, not mine.
So if the compiler knows with complete certainty there won't be an error, it shouldn't need me to tell it.
If functions return "error types" that need to be handled appropriately, well, the
Errors can be considered simply causing a different "stream" of code to get triggered, and besides defining what those "stream paths" (aka branches) are, I don't see why the programmer needs to specify anything else.
In C, I handle these "stream paths" with a
@taoeffect, look at more Zig code and it will become clear. A type that can also be a value can be split by if or switch. I think switch is the closest to
returns a 32-bit integer or an error.
Based on your example, you seem to want exceptions, even if they are local. Taking your example and rewriting in Zig (on the fly while I have no compiler in front of me, so please excuse the syntax errors!).
Now, this is not what I think of as idiomatic Zig, just a line-by-line translation. I think there is probably a better way to return errors than this but hopefully you can get the idea.
One thing that the goto+macros solution has that the
That's putting a little bit more faith in the compiler than Zig is comfortable doing right now.
The example of
Generally, the case of asserting that a function will never return an error is pretty rare, which is why I would like to get rid of the
Is the compiler less intelligent than the Java compiler? In Java it can tell whether exceptions might be thrown, and if you choose not to catch them you must write
Can't Zig work like this?
Then instead of saying "I know this won't cause an error", you
OK, I think my previous comment might have missed the point of the
If so, that is neat, and thanks @kyle-github for rewriting my code! That really helps me understand a lot better what's going on.
OK, now previous comment from @kyle-github is starting to make sense to me.
If so, OK, this makes a lot more sense to me, and yes, I would be in favor of removing
I will note, that in C, I wrote the
So what are the desirable properties here?
For me it would be:
In languages where the last expression is the value that's returned, the presence of the
In LISP, the solution here would be to wrap all of the function calls in an
For me, it simply does not get more elegant syntactically than LISP, but I understand you're not going to rewrite this language into s-exprs.
Zig already has
NOTE: edited the above code
Or something like that?
Sidenote: For me, it the meaning of
Sorry for filling up this thread with comments. Just one last thought and a comment:
I edited the above code to just use
The syntactic issue with C-based languages is that they create multiple namespaces unnecessarily.
For example, there's the namespace of "operators", which you'd better study and know by heart and not write them in the wrong place, and even though "operators" do basically the same thing as functions, they're separate in syntax and behavior from functions and the function namespace.
So programmers have to learn "two languages" (or more) when learning a "single" language.
Sexpr-based languages like LISP, Scheme and Clojure, simply do not have this problem.
You don't need to learn a new language to write an
So the sample code above would simply be:
(define (initKeychainAccess) (and (SecKeychainSetUserInteractionAllowed TRUE) (SecKeychainUnlock gKeychain 0 NULL FALSE) (SecKeychainAddCallback MyKeychainCallback kSecEveryEventMask NULL) (doThatFancyThingYouDo)))
Or me for that matter. There is a balance between simplicity and being concise enough to express powerful thoughts/code in a clean and efficient way. There are always people that like the extremes (APL on one end and Forth/Lisp on the other)!
Personally I like the idea of having some higher level constructs, but more oriented toward parallel execution with extremely easy fork/join semantics.
With your macros, as @thejoshwolfe mentioned, the debugging output on error is quite nice. This is one area where Perl's approach can be kind of interesting:
my $fh = open($filename,"<") or die "Oops, cannot open the file $filename";
That throws an exception which will be printed if not caught. I think in Zig this would be:
I assume here that
Heh, yeah, that's fine, just wanted to mention it. :-)
The idea behind
Having "or"s after every function that might fail will just get tiresome, hence
Here is the best example of error handling I've seen. Had to save it, it's that pretty.
Just add a bit of goto in there, to skip to the correct "destructor", and it's done. No code repeat.
It's kinda like defer, but I don't like defer very much, I think goto is more clear. I also want to have a programming language that I can use in repl terminal, and there's just no function end anywhere, defer will never get executed.
I also want a language that supports Structured Exception Handling, even thought I will probably only use 1 global handler instead of exception chain.
Just read this https://board.flatassembler.net/topic.php?t=20106
Oops. That's a typo in the docs.
If it's true that this expression:
Is equivalent to this:
This means if printf returns an error code, the program would crash!
I have to question why there's a need to crash the program if printf returned an error?
Why can't I just ignore it?
I don't even use the return value from printf for anything.
I like the go approach. Function returns multiple values, you can check the error value or explicitly ignore it.
Perhaps we can get another operator like %! to explicitly ignore errors instead of crashing on them.
I think are looking for the semantics of binding to an empty value to discard errors.
In anyway, my feeling is that code that can crash should be a bit more explicit.
I do like the idea that the error wraps a value, so that you need to unwrap it before accessing the value, similar to a null check on nullable types.
But you shouldn't be required to handle every possible error that every function can return.
I actually think it's ok to just ignore errors silently too. After all, if you're not using the return value of a function, does it really matter that it returned an error?
Using enums as a result type can avoid %'s a bit, though it's got it's own bookkeeping. There might be a more ergonomic way to attempt this, not sure.
Thinking on it more, my
I am also working on my own language (sexpr-based syntax) and traditional try/catch is what I'll be going with for that. That is what all other error handling techniques ultimately seem to boil down to, and it seems to be the simplest / purest form of error handling that I can think of.
I think the key issue I have is why consider "errors" as a special class of return values that must be handled otherwise the compiler complains? It should just be another value that the programmer is free to handle however he sees fit.
For example, if you call a function that performs some operation and returns a value, does the compiler force you to capture the return value and handle it?
If not, then error values should not be so special.
The only thing IMO the compiler should enforce is, when a function returns a union of types A | B, and the programmer wants to deal with the return as if it's type
Otherwise it should be ok to ignore the return value.
Perhaps it would be a good idea to take some inspiration from Nim: I really like the "discard" statement to ignore a return value. For a touch typer, typing a word can be easier than a symbol, and for someone reading the code, it's a lot clearer.
In addition to copying the "discard" keyword, Zig could add something like "noerror" (or "ignore", "safe", ...)
And to both discard and ignore the error:
Programmers are forgetful, and if they forget to check an error then critical code can fail unexpectedly. This mechanism of "handle all errors" makes it so the programmer is required to make a conscious decision about how to handle any error that may occur in the program. Ignoring error values because you don't think they contain anything can lead to numerous errors.
You're not, you're only required to handle the possibility of one error from failable functions. If you write code that doesn't cause errors, you don't need to handle them. Standard library calls like I/O always have a possibility of erroring, so we need to handle those. Having the program fail silently is bad for user experience.