Took an hour to speed run the implementation of the Result<T, E> type from rust in odin.
here: Result.odin
package main
import "core:fmt"
import rs "result"
safe_div :: proc(x: i32, y: i32) -> rs.Result(i32, string) {
if y == 0 do return rs.Err(string){"Divide by zero"}
return rs.Ok(i32){x / y}
}
main :: proc() {
x :: 100
y :: 0
answer := safe_div(x, y)
if rs.is_err(answer) do fmt.println("Error:", rs.unwrap_err(answer))
else do fmt.println(rs.unwrap(answer))
}
2 Likes
We don’t do r*st here. But anyway, thanks for sharing, looks nice!
4 Likes
It’s a cool excercise for applying functional programming to Odin, but realistically speaking …
Just returning multiple values is more comfy. You also get things like or_return
, or_break
, or_continue
and or_else
, that Rust wouldn’t be able to provide with their result types and you have to think 10x as hard to do the same thing conveniently. As much as I sometimes like functional programming, sometimes thinking about railroad programming and all of those higher-level concepts is too much.
4 Likes
I Only used it once, but there is the maybe keyword “maybe(int)”. For me its like the “using” keyword, Only good for prototyping. I regret using the “using” keyword everywhere months ago, It created more problems for me than solving them, so I’m scared of “maybe”.
using
, when used to bring struct fields into the same scope, makes everything very much like spaghetti: harder to reason about. I think @gingerBill has mentioned before that he’s had some regret about adding the feature. There’s even a warning for it when you use the -vet-using
flag:
‘using’ is considered bad practice outside of immediate refactoring.
Maybe(T)
is different; it’s effectively a union of T (and union
s in Odin have a nil
state, thus we get a nil
-able T), but as the overview says, it’s not as needed much with multiple return values. Personally, I hardly ever use Maybe(T)
, so it might be a code smell if you end up with a lot of them around.
1 Like
In general, I do not recommend trying to make Odin, which is a procedural language, into a functional language. Those ideas that you might take from other languages (e.g. Rust) will not really work well in Odin. Each language has a different way of tackling things, same with human languages. For example, Shakespeare could not be directly translated to French as French lacks the ability to do iambic pentameter (at least nicely).
Prefer multiple return values, even if this means there is a ternary state now rather than a binary state. The Result
approach is a binary state in Rust because it can either be the Ok
value or the Err
value, and not both. Whilst two return values containing the value and the error have three possible cases:
- Successful value + No error
- No value + Error
- Partially successful/failed value + Error
The fourth state “no value, no error” is technically the same as a “successful value, no error” state. Or if you want to be even more pedantic, this fourth state is the same as the implicit third state in the Result
type: result, error, invalid result. So even then the “binary” happens to be ternary .
That third state is actually a lot more useful than many people may realize, especially when you want to do not want stop execution early on and collect and collate the errors. This third state in the more functional cases is usually explicitly encoded with as a union in the result case
Looking at your result.odin
code, it’s technically not correct either because Odin’s union
have a nil
type. So your Result
union has three possible states to it: nil
, Ok
, and Err
. So the call is_err
is technically wrong too because it will be true for the nil
case too. This could be remedied with #no_nil
but then the default state is going to be either a zeroed Ok
or a zeroed
Err`, which might not be correct in either case.
And things like the unwrap_orelse
calls will not nice to use since Odin doesn’t support closures. This means that the procedure has either be “pure” or rely on globally accessible state.
4 Likes