可选类型 #
一、可选类型基础 #
1.1 什么是可选类型 #
可选类型表示一个值可能存在,也可能不存在(为 null):
zig
const std = @import("std");
pub fn main() void {
// 可选整数
const maybe_int: ?i32 = 42;
const no_int: ?i32 = null;
std.debug.print("maybe_int: {?}\n", .{maybe_int});
std.debug.print("no_int: {?}\n", .{no_int});
}
1.2 可选类型语法 #
使用 ?T 表示可选类型:
zig
const std = @import("std");
pub fn main() void {
const a: ?i32 = 10;
const b: ?[]const u8 = "hello";
const c: ?bool = null;
const d: ?f64 = 3.14;
std.debug.print("a: {?}\n", .{a});
std.debug.print("b: {?s}\n", .{b});
std.debug.print("c: {?}\n", .{c});
std.debug.print("d: {?}\n", .{d});
}
1.3 可选类型的内存布局 #
可选类型在内存中通常比基础类型多一个字节(用于标记是否有值):
zig
const std = @import("std");
pub fn main() void {
std.debug.print("i32 size: {}\n", .{@sizeOf(i32)});
std.debug.print("?i32 size: {}\n", .{@sizeOf(?i32)});
std.debug.print("bool size: {}\n", .{@sizeOf(bool)});
std.debug.print("?bool size: {}\n", .{@sizeOf(?bool)});
}
二、解包可选值 #
2.1 if 解包 #
zig
const std = @import("std");
pub fn main() void {
const maybe_value: ?i32 = 42;
if (maybe_value) |value| {
std.debug.print("Got value: {}\n", .{value});
} else {
std.debug.print("No value\n", .{});
}
}
2.2 while 解包 #
zig
const std = @import("std");
fn nextItem(items: []const i32, index: *usize) ?i32 {
if (index.* >= items.len) return null;
const item = items[index.*];
index.* += 1;
return item;
}
pub fn main() void {
const items = [_]i32{ 1, 2, 3, 4, 5 };
var index: usize = 0;
while (nextItem(&items, &index)) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
2.3 orelse 操作符 #
orelse 提供默认值:
zig
const std = @import("std");
pub fn main() void {
const maybe_value: ?i32 = null;
const value = maybe_value orelse 0;
std.debug.print("value: {}\n", .{value});
// 也可以使用表达式
const value2 = maybe_value orelse blk: {
std.debug.print("Using default\n", .{});
break :blk 100;
};
std.debug.print("value2: {}\n", .{value2});
}
2.4 .? 操作符 #
.? 解包可选值,如果为 null 则 panic:
zig
const std = @import("std");
pub fn main() void {
const maybe_value: ?i32 = 42;
const value = maybe_value.?;
std.debug.print("value: {}\n", .{value});
// 如果为 null,会 panic
// const no_value: ?i32 = null;
// const crash = no_value.?; // panic: attempt to unwrap null
}
三、可选指针 #
3.1 可选指针的特殊性 #
可选指针 ?*T 使用 0 表示 null,不需要额外空间:
zig
const std = @import("std");
pub fn main() void {
std.debug.print("*i32 size: {}\n", .{@sizeOf(*i32)});
std.debug.print("?*i32 size: {}\n", .{@sizeOf(?*i32)});
var value: i32 = 42;
const ptr: ?*i32 = &value;
if (ptr) |p| {
std.debug.print("value: {}\n", .{p.*});
}
}
3.2 可选切片 #
zig
const std = @import("std");
fn findSubstring(haystack: []const u8, needle: []const u8) ?[]const u8 {
if (std.mem.indexOf(u8, haystack, needle)) |index| {
return haystack[index..];
}
return null;
}
pub fn main() void {
const text = "Hello, World!";
if (findSubstring(text, "World")) |substr| {
std.debug.print("Found: {s}\n", .{substr});
} else {
std.debug.print("Not found\n", .{});
}
}
四、可选类型与函数 #
4.1 返回可选值 #
zig
const std = @import("std");
fn divide(a: i32, b: i32) ?i32 {
if (b == 0) return null;
return @divTrunc(a, b);
}
pub fn main() void {
const result1 = divide(10, 2);
const result2 = divide(10, 0);
std.debug.print("10 / 2 = {?}\n", .{result1});
std.debug.print("10 / 0 = {?}\n", .{result2});
}
4.2 可选参数 #
zig
const std = @import("std");
fn greet(name: ?[]const u8) void {
const actual_name = name orelse "Anonymous";
std.debug.print("Hello, {s}!\n", .{actual_name});
}
pub fn main() void {
greet(null);
greet("Alice");
}
4.3 可选结构体字段 #
zig
const std = @import("std");
const Person = struct {
name: []const u8,
age: ?u32,
email: ?[]const u8,
};
pub fn main() void {
const p1 = Person{
.name = "Alice",
.age = 30,
.email = null,
};
const p2 = Person{
.name = "Bob",
.age = null,
.email = "bob@example.com",
};
std.debug.print("{s}: age = {?}, email = {?s}\n", .{
p1.name,
p1.age,
p1.email,
});
std.debug.print("{s}: age = {?}, email = {?s}\n", .{
p2.name,
p2.age,
p2.email,
});
}
五、高级用法 #
5.1 可选类型的类型推断 #
zig
const std = @import("std");
pub fn main() void {
const a = null; // 类型为 @TypeOf(null)
const b: ?i32 = null;
const c: ?[]const u8 = null;
std.debug.print("a type: {}\n", .{@TypeOf(a)});
std.debug.print("b type: {}\n", .{@TypeOf(b)});
std.debug.print("c type: {}\n", .{@TypeOf(c)});
}
5.2 嵌套可选类型 #
zig
const std = @import("std");
pub fn main() void {
const nested: ??i32 = ?i32{ ?i32{ 42 } };
if (nested) |outer| {
if (outer) |inner| {
std.debug.print("value: {}\n", .{inner});
}
}
}
5.3 可选类型的比较 #
zig
const std = @import("std");
pub fn main() void {
const a: ?i32 = 42;
const b: ?i32 = 42;
const c: ?i32 = null;
std.debug.print("a == b: {}\n", .{a.? == b.?});
std.debug.print("a == null: {}\n", .{a == null});
std.debug.print("c == null: {}\n", .{c == null});
}
六、错误处理 vs 可选类型 #
6.1 何时使用可选类型 #
使用可选类型当:
- 缺失值是正常情况
- 不需要知道为什么缺失
- 简单的存在/不存在判断
zig
fn findItem(id: u32) ?Item {
if (hasItem(id)) {
return getItem(id);
}
return null;
}
6.2 何时使用错误联合类型 #
使用错误联合类型当:
- 缺失值是异常情况
- 需要知道失败原因
- 需要传播错误
zig
fn readItem(id: u32) !Item {
if (!hasItem(id)) {
return error.ItemNotFound;
}
if (!hasPermission(id)) {
return error.PermissionDenied;
}
return getItem(id);
}
6.3 转换 #
zig
const std = @import("std");
fn optionalToError(value: ?i32) !i32 {
return value orelse error.NoValue;
}
fn errorToOptional(value: anyerror!i32) ?i32 {
return value catch null;
}
pub fn main() void {
const opt: ?i32 = 42;
const err_val = optionalToError(opt) catch |e| {
std.debug.print("Error: {}\n", .{e});
return;
};
std.debug.print("Value: {}\n", .{err_val});
}
七、最佳实践 #
7.1 优先使用 orelse #
zig
// 好
const value = maybe_value orelse return error.NoValue;
// 避免
if (maybe_value) |v| {
// 使用 v
} else {
return error.NoValue;
}
7.2 避免过度使用 .? #
zig
// 好:有意义的错误处理
const value = maybe_value orelse return error.NoValue;
// 避免:可能 panic
const value = maybe_value.?;
7.3 使用 if 解包进行复杂逻辑 #
zig
const std = @import("std");
pub fn main() void {
const maybe_value: ?i32 = 42;
if (maybe_value) |value| {
if (value > 10) {
std.debug.print("Large value: {}\n", .{value});
} else {
std.debug.print("Small value: {}\n", .{value});
}
} else {
std.debug.print("No value\n", .{});
}
}
八、总结 #
可选类型要点:
| 操作 | 说明 |
|---|---|
?T |
可选类型声明 |
null |
空值 |
orelse |
提供默认值 |
.? |
解包(可能 panic) |
if |
安全解包 |
下一步,让我们学习错误联合类型!
最后更新:2026-03-27