2 个版本
0.0.2 | 2024年1月12日 |
---|---|
0.0.1 | 2024年1月11日 |
#728 in Rust 模式
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
-
addr_of!()
,因此.field
访问需要结果指针保持在同一 已分配对象 的范围内。这与offset()
的要求相同。 -
类似于 #1,
[index]
访问也要求结果指针在范围内。因为index
可能是任意表达式,并且切片以及数组都可以进行索引,因此无法在编译时断言。 -
此宏支持操作
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() |
分组 | ( ... ) |
仅为了清晰度将内部访问分组。 |
- `count`/`bytes`可以是整型字面量或括号内的表达式。
- 如果转换是组中的最后一个访问,则可以省略`=>`。
-
如果解除引用是宏中的最后一个访问,则它可能返回非指针值。
请注意,由于此操作在指针上调用`[read()]`,它很容易导致重复值。通常,仅在内部指针类型上使用此访问。
依赖项
~1.4–2MB
~41K SLoC