直接回答:核心思路是把"可空"从隐式默认值变成类型系统里的显式标记:类型默认不可为空,允许为空的必须写成 Optional[T](或 Kotlin 的 T?、Rust 的 Option<T>),编译器强制使用方处理空值分支。Tony Hoare 把 null 称为自己的"十亿美元错误",方案本质是让这个错误在编译期暴露。
具体设计:
- 类型层面区分可空/非空,非空类型禁止赋 null,编译检查;
- 提供安全的消费方式:模式匹配(
match强制覆盖 None 分支)、map/orElse组合子、空安全调用(?.)与默认值运算符(?:),避免手动 if-null 样板; - 边界转换:与外部系统(数据库、JSON、遗留库)交互时统一在边界处把"可能缺失"转成 Option,内部代码不再出现 null。
后果——收益:NPE 类线上事故基本消失,函数签名自文档化(看到 Optional 就知道要防空),重构更安全。代价:
- 互操作摩擦:与 Java 这类 null 渗透的语言交互时需要"平台类型"或边界校验(Kotlin 的痛点);
- 代码冗长与开销:层层 unwrap、包装对象的内存与间接成本;
- 学习成本:开发者要习惯用组合子而非直接判空;
- 迁移难:存量代码全量改造代价巨大,只能渐进推行(Python 可用
Optional+ mypy 部分实现,但没有运行时强制力)。
追问:Rust Option 为何没有包装开销(零成本抽象)、空值合并运传播中的短路语义。