- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Are the %%'s in front of functions necessary? #545
Comments
Have a look at http://ziglang.org/documentation/master/#errors
@thejoshwolfe is working on a proposal to get rid of the |
Related: #510 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 |
Do you have a proposal? |
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. Now with
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. |
There is some discussion about the use of defer that can also do some interesting things. I cannot remember the issue # though :-( |
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.
So 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 i.e. in JavaScript, something like 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 Maybe
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: CommentI edited the above code to just use
Last thoughtThe 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))) We could add type hints/info to this too (see Typed Clojure (note) or Typed Racket for inspiration). |
@taoeffect, I don't think you'll convert @andrewrk or @thejoshwolfe to Lisp-like syntax :-) 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 |
@taoeffect, oops, forgot to mention that there already is a |
Heh, yeah, that's fine, just wanted to mention it. :-) The
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. And about 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. An example:
|
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. |
Yes. test "foo" {
bar();
}
fn bar() -> bool {
return true;
}
|
I see, so like tiehuis mentioned earlier, my request is fulfilled by using:
I just tried in my project to replace |
You can do this: |
Related question. Followings can be built with no error. Is it a bug? const io = @import("std").io;
pub fn main() -> %void {
io.stdout.printf("no trailing semicolon\n")
} |
That works because the return value of printf is This just returns the value returned by printf from the main function. |
@tiehuis Thanks, if it's not a bug it's ok. |
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. |
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?
The text was updated successfully, but these errors were encountered: