深入解析 Rust 标准库 `is_null`:指针判空语义、宽指针陷阱与常量求值 panic 边界
发布时间:2026/9/10 1:34:28 作者:尧图编辑部 阅读量:1,286

深入解析 Rust 标准库is_null指针判空语义、宽指针陷阱与常量求值 panic 边界【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本篇技术指南围绕 Rust 编译器仓库rust-lang/rust 源码树中library/core标准库的*const T/*mut T指针判空方法is_null展开系统讲解其仅比较数据指针、不比较元数据的判空语义、在 unsized 类型宽指针/fat pointer上可能出现的两个 null 指针不相等陷阱以及常量求值const evaluation场景下因绝对地址未知而触发 panic 的边界条件。读完后你将掌握is_null的完整语义契约、底层实现机制const_eval_select双路径实现并能安全地在运行时与 const 上下文中使用指针判空逻辑。一、is_null的核心语义什么算空指针在 Rust 标准库中is_null是原始指针类型上的一个基础判定方法。该方法的官方文档定义如下原文位于 library/core/src/ptr/docs/is_null.mdReturnstrueif the pointer is null. —— 当指针为 null 时返回true。is_null与is_aligned、is_aligned_to等一样是*const T与*mut T的固有方法inherent method由 library/core/src/ptr/const_ptr.rs 与 library/core/src/ptr/mut_ptr.rs 中的impl块提供。它通常与标准库中的null()、null_mut()配套使用——这两个函数分别构造一个类型化typed的 null 共享指针与 null 可变指针定义见 library/core/src/ptr/mod.rs。// 运行时可用的判空示例源自 *const T 的文档示例 let s: str Follow the rabbit; let ptr: *const u8 s.as_ptr(); assert!(!ptr.is_null()); // 指向字符串数据的指针不为 null// 可变指针的判空示例源自 *mut T 的文档示例 let mut s [1, 2, 3]; let ptr: *mut u32 s.as_mut_ptr(); assert!(!ptr.is_null());判空只针对数据指针不涉及元数据原文档特别强调了一个容易忽视的关键点Note that unsized types have many possible null pointers, as only the raw data pointer is considered, not their length, vtable, etc. Therefore, two pointers that are null may still not compare equal to each other.翻译过来即对于 unsized 类型例如str、[T]、trait 对象dyn Trait存在很多种可能的 null 指针因为is_null只考察指针的裸数据指针raw data pointer部分而不考察长度length、虚表vtable等元数据。因此两个各自都为 null的指针仍然可能彼此不相等。这一点在实现层面有明确的印证。在 library/core/src/ptr/const_ptr.rs 的实现中第一步就是把宽指针fat pointer窄化// Compare via a cast to a thin pointer, so fat pointers are only // considering their data part for null-ness. let ptr self as *const u8;也就是说无论T是str、[u8]还是dyn Trait判空逻辑都会先把*const T强制转换cast成瘦指针*const u8然后只看这个数据地址是否为 0。长度字段、vtable 指针等元数据对判空结果完全无影响。实战启示对宽指针判空时p.is_null()与p null()并不等价。会比较完整指针数据指针 元数据而is_null()只比较数据指针部分。在 trait 对象、切片等场景下务必使用is_null()而不是依赖指针相等性来判断空指针。二、文档的共享机制一份文档两个方法共用你可能会好奇为什么is_null的文档存放在library/core/src/ptr/docs/这个专门目录里而不是直接写在const_ptr.rs或mut_ptr.rs的注释中答案在 library/core/src/ptr/docs/INFO.md 中这个目录存放的是原本会在可变指针与不可变指针之间重复复制的方法文档。之所以要独立成文件主要有三个原因示例不同可变/不可变指针的示例代码需要分别调用各自的方法*const T::is_null与*mut T::is_null是不同方法链接引用定义不同例如*const T::as_ref链接到*const T::is_null而*mut T::as_ref链接到*mut T::is_null可变指针的许多方法还会链接到返回可变引用的替代版本如as_mut_ref。在源码中该文档通过include_str!宏被两处共同引用library/core/src/ptr/const_ptr.rs#[doc include_str!(docs/is_null.md)]挂在implT: PointeeSized *const T的is_null上library/core/src/ptr/mut_ptr.rs同样的写法挂在implT: PointeeSized *mut T的is_null上。这样既保证了*const T与*mut T两处文档语义完全一致又避免了在源码中维护两份易漂移的重复文本——这是 Rust 标准库在文档工程上的一个值得借鉴的实践单一事实来源single source of truth。三、运行时实现*const T与*mut T的协作3.1*const T::is_null判空的真正实现*const T的is_null是完整的 const fn 实现位于 library/core/src/ptr/const_ptr.rs#[stable(feature rust1, since 1.0.0)] #[rustc_const_stable(feature const_ptr_is_null, since 1.84.0)] #[rustc_diagnostic_item ptr_const_is_null] #[inline] #[rustc_allow_const_fn_unstable(const_eval_select)] pub const fn is_null(self) - bool { // Compare via a cast to a thin pointer, so fat pointers are only // considering their data part for null-ness. let ptr self as *const u8; const_eval_select!( capture { ptr: *const u8 } - bool: // This use of const_raw_ptr_comparison has been explicitly blessed by t-lang. if const #[rustc_allow_const_fn_unstable(const_raw_ptr_comparison)] { match (ptr).guaranteed_eq(null_mut()) { Some(res) res, // To remain maximally conservative, we stop execution when we dont // know whether the pointer is null or not. // We can *not* return false here, that would be unsound in NonNull::new! None panic!(null-ness of this pointer cannot be determined in const context), } } else { ptr.addr() 0 } ) }其中#[rustc_const_stable(feature const_ptr_is_null, since 1.84.0)]表明is_null从 Rust 1.84 起在常量上下文中稳定可用。方法本身是#[inline]的配合#[stable(feature rust1, since 1.0.0)]自 1.0 起稳定可见它是一个被广泛依赖的基础设施方法。3.2*mut T::is_null一层轻量委托*mut T的版本更简洁位于 library/core/src/ptr/mut_ptr.rs直接委托给不可变版本pub const fn is_null(self) - bool { self.cast_const().is_null() }先通过cast_const()把*mut T转成*const T再复用同一套判空逻辑避免了双份实现。3.3 运行时路径addr() 0在普通运行时const_eval_select!的 else 分支判空退化为一次简单的地址比较ptr.addr() 0。addr()返回指针的裸地址usize因此运行时判空就是地址是否为 0的整数比较这是一个零成本、可直接内联的操作。3.4 与NonNull的联动为什么判空不能出错is_null的准确性直接关系到NonNull的安全性。在 library/core/src/ptr/non_null.rs 中NonNull::new的构造函数正是以is_null为判定依据pub const fn new(ptr: *mut T) - OptionSelf { if !ptr.is_null() { // SAFETY: The pointer is already checked and is not null Some(unsafe { Self::new_unchecked(ptr) }) } else { None } }而NonNull::new_unchecked的 UB 前置条件检查library/core/src/ptr/non_null.rs同样复用了is_nullassert_unsafe_precondition!( check_language_ub, NonNull::new_unchecked requires that the pointer is non-null, (ptr: *mut () ptr as *mut ()) !ptr.is_null() );这也是源码注释中强调我们不能在无法判断时返回false那会使NonNull::new不健全unsound的原因——如果is_null在常量求值无法确定结果时错误地返回falseNonNull::new就会把一个可能是 null 的指针包装成非空指针破坏NonNull的永远非空语言保证。四、常量求值期间的 panic 边界这是is_null文档中最重要、也最容易被忽视的部分。原文档原文如下If this method is used during const evaluation, andselfis a pointer that is offset beyond the bounds of the memory it initially pointed to, then there might not be enough information to determine whether the pointer is null. This is because the absolute address in memory is not known at compile time. If the nullness of the pointer cannot be determined, this method will panic.In-bounds pointers are never null, so the method will never panic for such pointers.归纳出两条明确的边界规则越界out-of-bounds指针可能 panic在 const 上下文中如果self是通过offset/byte_add等方式偏移到超出其最初所指向内存边界之外的指针那么编译器可能没有足够信息判断它是否为 null——因为编译期并不知道指针的绝对内存地址。当 nullness 无法确定时is_null会直接 panic。界内in-bounds指针永不 panic指向合法对象内部的指针绝不可能是 null因此对这类指针调用is_null永远不会 panic。这一行为在实现层面有非常清晰的体现。const 分支走的是guaranteed_eq(null_mut())该方法定义见 library/core/src/ptr/const_ptr.rs返回Optionbool运行时它等价于Some(self other)但在编译期求值等场景下并不总能确定两个指针的相等性此时会虚假地spuriously返回None返回Some时相等性才是被保证已知的。is_null对guaranteed_eq的结果做 match 处理match (ptr).guaranteed_eq(null_mut()) { Some(res) res, // 能确定直接返回 None panic!(null-ness of this pointer cannot be determined in const context), }即一旦编译期无法判定选择 panic 而不是猜测——这是最大限度地保守maximally conservative的设计决策宁可中止求值也不给出可能错误的判定。相关的guaranteed_eq目前仍处于const_raw_ptr_comparison不稳定特性issue #53020之下但它在is_null内部的这处使用已获得 t-lang 的显式许可源码注释 This use ofconst_raw_ptr_comparisonhas been explicitly blessed by t-lang。一个简单的理解模型常量求值 在编译器里跑程序此时堆上真实地址未知指针大多以分配 ID 偏移的抽象形式存在对一个界内指针判空编译器知道它来自某个分配不可能是地址 0直接给出false对一个偏移出界的指针判空它的绝对地址在数学上既可能回绕到 0也可能不是编译期无法决定于是 panic。NonNull::new的文档也同步记录了这一约束library/core/src/ptr/non_null.rsThis method will panic during const evaluation if the pointer cannot be determined to be null or not. Seeis_nullfor more information.五、实战要点速查宽指针判空用is_null()不要依赖判空is_null只比较数据指针部分长度/vtable 等元数据不参与两个数据地址为 0 的宽指针例如两个不同的 null trait 对象指针彼此可能为false。运行时判空是零成本的最终退化为ptr.addr() 0的整数比较配合#[inline]可放心在热路径使用。const 上下文中对偏移出界的指针调用is_null可能 panic原因是编译期不知道绝对地址而界内指针判空永不 panic。若在 const fn 中需要对可能越界的指针判空需自行保证指针处于界内或准备好接受 panic 中止求值。is_null是NonNull安全性的基石NonNull::new与new_unchecked的 UB 前置检查都依赖它因此标准库在无法判定时选择 panic 而非返回false以维持非空指针的语言级保证。文档与实现分离是标准库的工程实践docs/is_null.md通过include_str!同时供*const T与*mut T复用保证了两个方法文档语义完全一致见 library/core/src/ptr/docs/INFO.md。六、进一步阅读方法文档原文library/core/src/ptr/docs/is_null.md*const T实现与示例library/core/src/ptr/const_ptr.rs*mut T实现与示例library/core/src/ptr/mut_ptr.rsguaranteed_eq语义说明library/core/src/ptr/const_ptr.rsNonNull::new/new_unchecked与is_null的联动library/core/src/ptr/non_null.rsnull()/null_mut()构造函数library/core/src/ptr/mod.rs共享文档机制说明library/core/src/ptr/docs/INFO.md【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考