So you have to understand that the reason @(require_results) is NOT the default is because Odin is tailoring more to C programmers who are used to this. Other languages may require you to do the _ = foo() approach then opt into the “allow results to be ignored” behaviour. However, because we are tailoring to C programmers, this will annoy them more than you realize to the point of them getting angry.
You might think it’s a nice way to prevent mistakes, but it’s a trade-off between “minimizing mistakes” (which is still an empirical statement by the way) and “annoying C programmers too much”.
This is fairly minor, subjective and a nit pick but I wish Odin also supported while keyword which does nothing more than for { } or while could just be an alias for for.
May be it’s me personally, it always takes a bit of time to figure out for me mentally if it’s a unbounded for { } loop or a bounded for iteration loop.
You might think it’s a nice way to prevent mistakes, but it’s a trade-off between “minimizing mistakes” (which is still an empirical statement by the way) and “annoying C programmers too much”.
Surely there’s a better reason than not having to type a couple characters in exchange with catching the following, not to mention we could always introduce a flag/file-scoped option to make it more lax:
package main
import "core:fmt"
main :: proc() {
i: int
i_init(&i)
// Whoops, we didn't set the value!
fmt.println(i)
// User's notified return values must be handled.
// If this is not wanted, it's at this time the user may correct it with
// `@(optional_last_result)` (explained below) or `@(optional_results)`.
i_init_fixed(&i)
}
Error :: enum { Out_Of_Memory }
i_init :: proc(i: ^int) -> Error {
if true { return Error.Out_Of_Memory }
i^ = 12
return nil
}
@(require_results) i_init_fixed :: proc(i: ^int) -> Error {
if true { return Error.Out_Of_Memory }
i^ = 12
return nil
}
I tested several (imperative) languages to see where they were at, and Zig was the only one that actually caught this without a lint, so… I’m assuming I’m missing something at this point, but no one has articulated what it is, and it’s an important feature of every linter I can think of.
Another good reason is that the amount of application code (outside of the index value in for ... in loops, assuming that counts) that’s actually okay with ignoring results is insanely small.
I do understand there’s more cases in the standard library itself.
Reversing @(require_results) and possibly replacing #optional_allocator_error with one that more generally ignores the right-most return value (@(optional_last_result) or something) allows for flexibility in case ignoring the last value specifically, is wanted.
At the end of the day, I’m fine with creating lints or forking the compiler, but yeah. Just one of those things that’s caused many headaches these last few decades, until linters were more commonly used to catch mistakes.
I’m certainly no language designer, which is why I’m interested in any knowledge I may have missed.
Seems like the majority of core code doesn’t use require_results, even though in a lot of cases it makes sense to use it, especially for functions that can return an error.
I’m one of the people that gets annoyed by @require_results usage a lot, when it’s used on functions with side effects I often don’t care about the result and am just calling the function for the side effect.
IMO @require_results should only be added on functions without side effects.
I actually mentioned just how nice it is for your case, because it’s something you’ll instantly be aware of and have an easy resolution to—use _ =, submit a pull request, log a bug, or if you don’t mind modifying the standard library while you wait:
// If this is not wanted, it’s at this time the user may correct it with
// @(optional_last_result) (explained below) or @(optional_results).
Unfortunately, the opposite is not true. I’d rather you be slightly annoyed than developers and/or clients wasting a bunch of time on a bug we could have prevented.
How many cases are there where a function returns but the return can just be ignored / is not important? I would say the vast majority of times if a function is returning something, the return value is meant to be used. And the default should reflect that. Currently, you have to think about opting in to this feature which means that in a lot of cases where require_results makes sense don’t get annotated as such because it never occurred to the author of the code.
Biggest issues i have is the bindings, but these are sort of non solvable.
Like binding things like mbedTLS is painful one could use a tool todo it but it really doesn’t work for a lot of cases and creates awful code so you end up going down the rabbit hole of porting it all yourself.
I think though that this i still the better way than doing it like Zig where it basically has to ship an entire C Preproc and Lexer/Parser in order to natively add headers.
Another thing is the missing meta programming, which adds a lot of clutter to code mostly because the code i write at work is sort of like check all pointers to be not null, fall in range etc. with a meta program i could add all of this. I dunno about the state of core:odin and if that somehow could be used to add this stuff. But i also understand that the language should be more C like and there it will take quite some time till they get anything like that in C++ it will be there probably in C++29.
Also a reason why i’d like to have this option is to generate code from specification files i can basically already do that as of a sort of preprocessor but it sort of means maintaining a extra compiler just todo this translation. Its like Qmoc.