可选类型 #

一、可选类型基础 #

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