I’m not familiar with zig. Can you give some examples to illustrate your point?
For instance a function which returns an optional pointer to a 'Bla':
fn make_bla() ?*Bla {
// this would either return a valid *Bla, or null
}
A null pointer can't be used accidentally, it must be unwrapped first, and in Zig this is implemented as language syntax, for instance you can unwrap with an if: if (make_bla()) |bla| {
// bla is now the unwrapped valid pointer
} else {
// make_bla() returned null
}
...or with an orelse: const bla = make_bla() orelse { return error.InvalidBla };
...or if you know for sure that bla should be valid, and otherwise want a panic: const bla = make_bla().?;
...error handling with error unions has similar syntax sugar.It's probably not perfect, but I feel that for real-world code, working with optionals and errors in Zig leads to more readable code on average than Rust, while providing the same set of features.
The main difference I see is that in Rust it will also work with your own custom types, not just optional.
fn make_bla() -> Option<Bla> {
// this either returns a valid Bla, or None
}
if let Some(bla) = make_bla() {
// bla is now the unwrapped valid type
} else {
// make_bla() returned None
}
..or with the '?' operator (early return) let bla = make_bla().ok_or(InvalidBla)?;
..or with let_else (nightly only but should be stable Soon(tm)) let Some(bla) = make_bla() else { return Err(InvalidBla) }
..or panic on None let bla = make_bla().unwrap(); ??i32 const assert = @import("std").debug.assert;
fn get12() i32 {
const opt_opt_val: ??i32 = 12;
const val = opt_opt_val.?.?;
comptime assert(val == 12);
return val;
}
[https://www.godbolt.org/z/oncd5shvP]