#raw-pointers #macro #access #elements #ptr #accessing #index

element-ptr

一个宏,使通过原始指针访问元素变得更加容易

2 个版本

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

#728 in Rust 模式

MIT 许可证

26KB
183

元素指针

此 crate 暴露了一个宏,使处理原始指针变得容易。

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 永远不在范围内。

语法 & 语义

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

访问类型 语法 等价的指针表达式
字段 .field addr_of!((*ptr).field)
索引 [index] ptr.cast::<T>().add(index)
添加偏移量 +count 1 ptr.add(count)
减去偏移量 -count 1 ptr.sub(count)
字节添加偏移量 u8+bytes 1 ptr.byte_add(bytes)
字节减去偏移量 u8-bytes 1 ptr.byte_sub(bytes)
转换 asT=> 2 ptr.cast::<T>()
解除引用 .* 3 ptr.read()
分组 ( ... ) 仅为了清晰度将内部访问分组。
  1. `count`/`bytes`可以是整型字面量或括号内的表达式。
  2. 如果转换是组中的最后一个访问,则可以省略`=>`。
  3. 如果解除引用是宏中的最后一个访问,则它可能返回非指针值。
    请注意,由于此操作在指针上调用`[read()]`,它很容易导致重复值。通常,仅在内部指针类型上使用此访问。

依赖项

~1.4–2MB
~41K SLoC