#pointers #raw-pointers #ptr #element #macro #index #element-ptr

element-ptr-macro

element-ptr 的内部宏库

2 个版本

0.0.2 2024年1月12日
0.0.1 2024年1月11日

#21 in #ptr


用于 element-ptr

MIT 许可证

16KB
393

元素指针

此库公开了一个宏,可以简化处理原始指针的过程。

在Rust中使用原始指针时,一个常见的问题是在通过原始指针遍历结构时难以安全地处理它们。一些困难包括

  • 通过原始指针遍历结构时创建中间引用是危险的,也可能是不正确的。这就是为什么存在 addr_of!() 的原因。

  • 即使有了 addr_of!(),其重复使用的语法看起来也可能非常糟糕。每次 .field 访问都需要像 (*ptr).field 这样,而且宏调用本身也造成了大量杂乱。

  • NonNull<T> 的使用非常令人烦恼。它缺少如 offset() 这样的方法,并且不能与前面提到的 addr_of!() 一起使用。这意味着使用 NonNull<T> 的任何东西都必须不断地在普通原始指针和它之间进行转换。

  • 没有简洁的方法可以从 *const [T; L] 中获取一个元素的原始指针。正确的方法是 ptr.cast::<T>().add(index),但指定转换类型很啰嗦,不指定可能会导致编译器错误。

这个crate试图通过一个综合宏来解决这个问题。

示例

以下示例详细说明了宏的基本用法。

use element_ptr::element_ptr;

struct BaseStruct {
    first: u32,
    second: ChildStruct,
}

struct ChildStruct {
    elements: [u32; 10],
}

unsafe fn get_child_element_ptr(
    ptr: *const BaseStruct,
    index: usize
) -> *const u32 {
    element_ptr!(ptr => .second.elements[index])
}

该宏本身使用特殊语法来描述指针应该如何移动。

首先,调用宏并传入基本指针。这可能是指针类型有效的任何表达式。

element_ptr!(ptr => /* ... */ )

接下来,语法

.second

使指针移动到 second 字段的地址。这是最基本的操作类型,可能也是最常用的。也可以使用整数来索引元组。

然后访问另一个字段。注意,在前一个访问之后,指针是 *const ChildStruct。这意味着 .elements 将访问内部 ChildStruct 中的 elements 字段。因为结构体是按值存储的,所以不需要解引用指针。

然后,使用以下方式访问数组的索引

[index]

宏将静态检查以确保仅在使用可索引的引用类型时使用索引语法。index 可以是任何表达式,而不仅仅是变量名或静态值。

最后,该宏返回最后访问的子元素的指针,在这种情况下,是 second.elements 中十 u32 之一。

安全性

由于以下几个原因,调用此宏始终是 unsafe

  1. addr_of!(),因此 .field 访问需要确保结果指针位于同一 分配对象 的范围内。这是与 offset() 相同的要求。

  2. 类似于 #1,[index] 访问也需要确保结果指针在范围内。因为 index 可以是任意表达式,以及切片和数组都可以被索引,所以这不能在编译时断言。

  3. 此宏支持操作 NonNull<T>,因此任何偏移量都可能使指针移动到空指针,导致UB。这几乎总是与 #1 相同,因为地址 0 永远不在范围内。

语法 & 语义

有多种元素访问类型,每种都可以执行不同的操作。除了 .* 之外,它们都不会解引用指针。

访问类型 语法 等价的指针表达式
字段 .字段 addr_of!((*ptr).field)
索引 [索引] ptr.cast::<T>().add(index)
增加偏移量 +计数 1 ptr.add(count)
减少偏移量 -计数 1 ptr.sub(count)
字节增加偏移量 u8+字节 1 ptr.byte_add(bytes)
字节减少偏移量 u8-字节 1 ptr.byte_sub(bytes)
转换 asT=> 2 ptr.cast::<T>()
解引用 .* 3 ptr.read()
分组 ( ... ) 仅为了清晰起见,对内部访问进行分组。
  1. count/bytes可以是整数文字或括号内包裹的表达式。
  2. 如果转换是组中的最后一个访问,则可以省略=>
  3. 只有当解引用是宏的最后一个访问时,它才可能返回一个非指针值。
    请注意,因为这个函数在指针上调用read(),它很容易导致重复值。通常,仅对内部指针类型使用此访问。

依赖关系

~1.4–2MB
~41K SLoC