This is an automated archive made by the Lemmit Bot.
The original was posted on /r/programminglanguages by /u/DoomCrystal on 2024-03-25 03:14:00.
I'm new to this type theory business, so bear with me :) Questions are at the bottom of the post.
I've been trying to learn about how different languages do things, having come from mostly a C background (and more recently, Zig). I just have a few questions about how languages do optionals differently from something like Zig, and what approaches might be best.
Here is the reference for Zig's optionals if you're unfamiliar:
From what I've seen, there's sort of two paths for an 'optional' type: a true optional, like Rust's "Some(x) | None", or a "nullable" types, like Java's Nullable. Normally I see the downsides being that optional types can be verbose (needing to write a variant of Some() everywhere), whereas nullable types can't be nested well (nullable nullable x == nullable x). I was surprised to find out in my investigation that Zig appears to kind of solve both of these problems?
A lot of times when talking about the problem of nesting nullable types, a "get" function for a hashmap is brought up, where the "value" of that map is itself nullable. This is what that might look like in Zig:
const std = @import("std");
fn get(x: u32) ??u32 {
if (x == 0) {
return null;
} else if (x == 1) {
return @as(?u32, null);
} else {
return x;
}
}
pub fn main() void {
std.debug.print(
"{?d} {?d} {?d}\n",
.{get(0) orelse 17, get(1) orelse 17, get(2) orelse 17},
);
}
- We return "null" on the value 0. This means the map does not contain a value at key 0.
- We cast "null" to ?u32 on value 1. This means the map does contain a value at key 1; the value null.
- Otherwise, give the normal value.
The output printed is "17 null 2\n". So, we printed the "default" value of 17 on the ??u32
null case, and we printed the null directly in the ?u32
null case. We were able to disambiguate them! And in this case, the some() case is not annotated at all.
Okay, questions about this.
- Does this really "solve" the common problems with nullable types losing information and optional types being verbose, or am I missing something? I suppose the middle case where a cast is necessary is a bit verbose, but for single-layer optionals (the common case), this is never necessary.
- The only downside I can see with this system is that an optional of type
@TypeOf(null)
is disallowed, and will result in a compiler error. In Zig, the type of null is a special type which is rarely directly used, so this doesn't really come up. However, if I understand correctly, because null is the only value that a variable of the type @TypeOf(null)
can take, this functions essentially like a Unit type, correct? In languages where the unit type is more commonly used (I'm not sure if it even is), could this become a problem?
- Are these any other major downsides you can see with this kind of system besides #2?
- Are there any other languages I'm just not familiar with that already use this system?
Thanks for your help!