element_ptr!() { /* proc-macro */ }
Expand description
Returns the address of an inner element without creating unneeded intermediate references.
The general syntax is
element_ptr!(base_ptr => /* element accesses */ )
where base_ptr
may be any expression that evaluates to a value of the following types:
All accesses (besides a dereference) will maintain that pointer type of the input pointer.
This is especially nice with NonNull<T>
because it makes everything involving it much
more ergonomic.
§Element accesses
The following a table describes each of the possible accesses that can be inside the macro. These can all be chained by simply putting one after another.
Access Kind | Syntax | Equivalent Pointer Expression | |
---|---|---|---|
Field | .field | addr_of!((*ptr).field) | |
Index | [index] | ptr.cast::<T>().add(index) | |
Add Offset | + count | 1 | ptr.add(count) |
Sub Offset | - count | 1 | ptr.sub(count) |
Byte Add Offset | u8+ bytes | 1 | ptr.byte_add(bytes) |
Byte Sub Offset | u8- bytes | 1 | ptr.byte_sub(bytes) |
Cast | as T => | 2 | ptr.cast::<T>() |
Dereference | .* | 3 | ptr.read() |
Grouping | ( ... ) | Just groups the inner accesses for clarity. |
- `count`/`bytes` may either be an integer literal or an expression wrapped in parentheses.
- The `=>` may be omitted if the cast is the last access in a group.
- A dereference may return a value that is not a pointer only if it is the final access in the macro. In general it is encouraged to not do this and only use deferencing for inner pointers.
§Safety
- All of the requirements for
offset()
must be upheld. This is relevant for every access except for dereferencing, grouping, and casting. - The derefence access (
.*
) unconditionally reads from the pointer, and must not violate any requirements related to that.
§Examples
The following example should give you a general sense of what the macro is capable of, as well as a pretty good reference for how to use it.
use element_ptr::element_ptr;
use std::{
alloc::{alloc, dealloc, Layout, handle_alloc_error},
ptr,
};
struct Example {
field_one: u32,
uninit: u32,
child_struct: ChildStruct,
another: *mut Example,
}
struct ChildStruct {
data: [&'static str; 6],
}
let example = unsafe {
// allocate two `Example`s on the heap, and then initialize them part by part.
let layout = Layout::new::<Example>();
let example = alloc(layout).cast::<Example>();
if example.is_null() { handle_alloc_error(layout) };
let other_example = alloc(layout).cast::<Example>();
if other_example.is_null() { handle_alloc_error(layout) };
// Get the pointer to `field_one` and initialize it.
element_ptr!(example => .field_one).write(100u32);
// But the `uninit` field isn't initialized.
// We can't take a reference to the struct without causing UB!
// Now initialize the child struct.
let string = "It is normally such a pain to manipulate raw pointers, isn't it?";
// Get each word from the sentence
for (index, word) in string.split(' ').enumerate() {
// and push alternating words to each child struct.
if index % 2 == 0 {
// The index can be any arbitrary expression that evaluates to an usize.
element_ptr!(example => .child_struct.data[index / 2]).write(word);
} else {
element_ptr!(other_example => .child_struct.data[index / 2]).write(word);
}
}
element_ptr!(example => .another).write(other_example);
example
};
// Now that the data is initialized, we can read data from the structs.
unsafe {
// The `element_ptr!` macro will get a raw pointer to the data.
let field_one_ptr: *mut u32 = element_ptr!(example => .field_one);
// This means you can even get a pointer to a field that is not initialized.
let uninit_field_ptr: *mut u32 = element_ptr!(example => .uninit);
assert_eq!(*field_one_ptr, 100);
let seventh_word = element_ptr!(example => .child_struct.data[3]);
assert_eq!(*seventh_word, "to");
// The `.*` access is used here to go into the pointer to `other_example`.
// Note that this requires the field `another` to be initialized, but not any
// of the other fields in `example`.
// As long as you don't use `.*`, you can be confident that no data will ever
// be dereferenced.
let second_word = element_ptr!(
example => .another.*.child_struct.data[0]
);
assert_eq!(*second_word, "is");
// Now lets deallocate everything so MIRI doesn't yell at me for leaking memory.
let layout = Layout::new::<Example>();
// Here as a convenience, we can cast the pointer to another type using `as T`.
dealloc(element_ptr!(example => .another.* as u8), layout);
// Of course this is simply the same as using `as *mut T`
dealloc(example as *mut u8, layout);
}