Why specialized keywords such as `or_return`, `or_else`, etc. instead of `or`?

This question is out of curiosity/interest on the language design. Why was the or_else, or_return, or_continue, or_break keywords chosen instead of, say, an or keyword which can be used/combined with the existing control flow keywords (break, return, continue) or an expression (to be equivalent to the current or_else). Is this an implementation detail in the compiler (I assume not, as parsing this is possible) or a decision to communicate to the programmer the limitations of what an “or” can do.

Said with code, why not:

v: union{int, f64}
i: int
i = v.(int) or 123 // instead of or_else
i = v.? or 123 // Type inference magic
assert(i == 123)

i.e. if or is given an expression it acts as or_else

y := caller_2() or return

i.e. if or is given a statement (return) it acts like or_return, or_continue, or_break

That is, here or is a binary operator that accepts an expression or statement on the RHS

Similar for in and not_in, i.e. why not the not in keyword similar to python? For this instance, is it due to readability/consistency? i.e. keywords do not contain spaces similar to identifiers?

4 Likes

I am not Ginger Bill, nor can I read minds. That being said…
Odin explicitly aims for simplicity and readability. Keywords like or, and, and not are associated with logic and bitwise operations, even though Odin does not use them as such. The or_… family of keywords are for control flow, and are quite distinct. They stand out and grab your attention - as they should.

Also, single keywords are easier to parse and experiment with. :smirk:

2 Likes

I’d actually argue that having a closed set of keywords is easier to experiment with than adding new keywords for specific use cases. Having or and return as separate keywords isn’t that much more difficult to parse than or_return, while eliminating the possibility of conflicting with potential variable and function names from existing programs.

A little bit offtopic:
When I started using Odin, I had problems using or_return the right way because it was not intuitive for me. And I think it is not consistence to the other “or_” keywords. It is not possible to use or_return in a void proc to ignore/swallow the error and just return from the proc. Like or_continue/or_break allow exiting the scope without any error propagation. These two behave more like their normal counterparts continue/break.

Maybe it is because the current or_return means or_rethrow/or_yield in practice.

Does not compile:

greet_only_numbers :: proc(input: string) {
    number := strconv.parse_int(input) or_return 
    fmt.printfln("Hello, %v!", number)
}

I even started to think that or_return should also allow to specify a result on the right hand side, like or_else does. So it would be possible to map errors to custom enum types. Or return a default value from the proc (like or_else does for the assignment).

Does not compile:

Greeting_Error :: enum {
   None,
   Not_A_Number,
}

greet_only_numbers :: proc(input: string) -> Greeting_Error {
    number := strconv.parse_int(input) or_return .Not_A_Number
    
    fmt.printfln("Hello, %v!", number)
    return .None
}

And yes I also would prefer “or return” aesthetically :wink:
Because there is also also the do keyword in the language:

if !ok do return

So do after expressions and your suggested or after assignments could make sense. But then or return must behave more like a regular return statement and not in the current special way.

2 Likes

I experimented with or return etc but found it to be more of a problem in practice because it kind of sullied the idea. If or existed, it kind of suggests that you also want and and to replace || and &&, respectively. I did not want to go down that route.

Also adding or would have reduced the total number of keywords, but also introduce possible scanning problems since or return would also be allowed.

In practice, or_return etc are not a problem being separate keywords, especially when they have a specific meaning to them. It doesn’t really need to be generalized further.

2 Likes

it kind of suggests that you also want and and to replace || and &&, respectively. I did not want to go down that route.

Fair enough.

Though after musing about this, I kind of like the idea of and - it would basically act as a short-hand if statement for error checking, but without the support for an else branch (unless added). Though, this has slightly different semantics than or, as or returns a value from RHS vs. RHS is evaluated for an and, i.e. this would be possible

data := os.read_entire_file("my_file") and {
     fmt.println("read my_file succesfully")
     // ...
}

instead of

if data, ok := os.read_entire_file("my_file"); ok {
     fmt.println("read my_file succesfully")
     // ...
}

I understand that it’s two way of doing the same thing, which is something you’re philosophically against.

but also introduce possible scanning problems since or return would also be allowed

When you say scanning, do you mean w.r.t someone (a person) scanning the code as opposed to the compiler (as the compiler is whitespace insensitive)? Arguably odinfmt handles this case.

data := os.read_entire_file("my_file") and {
     fmt.println("read my_file succesfully")
     // ...
}

The reason was never allowed is for two reasons:

  • You cannot query the results any more
  • It mixes statements and expressions

And the second aspect was a huge aspect of design for Odin. I wanted to keep statements and expressions separate where possible. This is to allow things to be clearer to scan code. You can clearly see blocks of statements and that approach would not make it clear at all.

1 Like