mirror of
https://github.com/Lokathor/bytemuck.git
synced 2024-11-21 22:32:23 +00:00
update offset_of to have 2-arg and 3-arg versions.
This commit is contained in:
parent
f9849c63c7
commit
c9a387a8da
128
src/offset_of.rs
128
src/offset_of.rs
@ -1,89 +1,68 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
/// Find the offset in bytes of the given `$field` of `$Type`, using `$instance`
|
/// Find the offset in bytes of the given `$field` of `$Type`. Requires an
|
||||||
/// as an already-initialized value to work with.
|
/// already initialized `$instance` value to work with.
|
||||||
///
|
///
|
||||||
/// This is similar to the macro from `memoffset`, however it's fully well
|
/// This is similar to the macro from [`memoffset`](https://docs.rs/memoffset),
|
||||||
/// defined even in current versions of Rust (and uses no unsafe code).
|
/// however it uses no `unsafe` code.
|
||||||
///
|
///
|
||||||
/// It does by using the `$instance` argument to have an already-initialized
|
/// This macro has a 3-argument and 2-argument version.
|
||||||
/// instance of `$Type` rather than trying to find a way access the fields of an
|
/// * In the 3-arg version you specify an instance of the type, the type itself,
|
||||||
/// uninitialized one without hitting soundness problems. The value passed to
|
/// and the field name.
|
||||||
/// the macro is referenced but not moved.
|
/// * In the 2-arg version the macro will call the [`default`](Default::default)
|
||||||
|
/// method to make a temporary instance of the type for you.
|
||||||
///
|
///
|
||||||
/// This means the API is more limited, but it's also sound even in rather
|
/// The output of this macro is the byte offset of the field (as a `usize`). The
|
||||||
/// extreme cases, like some of the examples.
|
/// calculations of the macro are fixed across the entire program, but if the
|
||||||
|
/// type used is `repr(Rust)` then they're *not* fixed across compilations or
|
||||||
|
/// compilers.
|
||||||
///
|
///
|
||||||
/// ## Caveats
|
/// **CAUTION:** It is **unsound** to use this macro with a `repr(packed)` type.
|
||||||
///
|
/// Currently this will give a warning, and in the future it will become a hard
|
||||||
/// 1. The offset is in bytes, and so you will likely have to cast your base
|
/// error.
|
||||||
/// pointers to `*const u8`/`*mut u8` before getting field addresses.
|
/// * See [rust-lang/rust#27060](https://github.com/rust-lang/rust/issues/27060)
|
||||||
///
|
/// for more info and for status updates.
|
||||||
/// 2. The offset values of repr(Rust) types are not stable, and may change
|
/// * Once this issue is resolved, a future version of this crate will use
|
||||||
/// wildly between releases of the compiler. Use repr(C) if you can.
|
/// `raw_ref` to correct the issue. For the duration of the `1.x` version of
|
||||||
///
|
/// this crate it will be an on-by-default cargo feature (to maintain minimum
|
||||||
/// 3. The value of the `$instance` parameter has no bearing on the output of
|
/// rust version support).
|
||||||
/// this macro. It is just used to avoid soundness problems. The only
|
|
||||||
/// requirement is that it be initialized. In particular, the value returned
|
|
||||||
/// is not a field pointer, or anything like that.
|
|
||||||
///
|
///
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
///
|
///
|
||||||
/// ### Use with zeroable types
|
/// ### 3-arg Usage
|
||||||
/// A common requirement in GPU apis is to specify the layout of vertices. These
|
///
|
||||||
/// will generally be [`Zeroable`] (if not [`Pod`]), and are a good fit for
|
/// ```rust
|
||||||
/// `offset_of!`.
|
/// # use bytemuck::offset_of;
|
||||||
|
/// // enums can't derive default, and for this example we don't pick one
|
||||||
|
/// enum MyExampleEnum {
|
||||||
|
/// A, B, C,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // so now our struct here doesn't have Default
|
||||||
|
/// #[repr(C)]
|
||||||
|
/// struct MyNotDefaultType {
|
||||||
|
/// pub counter: i32,
|
||||||
|
/// pub some_field: MyExampleEnum,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // but we provide an instance of the type and it's all good.
|
||||||
|
/// let val = MyNotDefaultType { counter: 5, some_field: MyExampleEnum::A };
|
||||||
|
/// assert_eq!(offset_of!(val, MyNotDefaultType, some_field), 4);
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bytemuck::{Zeroable, offset_of};
|
///
|
||||||
|
/// ### 2-arg Usage
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use bytemuck::offset_of;
|
||||||
|
/// #[derive(Default)]
|
||||||
/// #[repr(C)]
|
/// #[repr(C)]
|
||||||
/// struct Vertex {
|
/// struct Vertex {
|
||||||
/// pos: [f32; 2],
|
/// pub loc: [f32; 3],
|
||||||
/// uv: [u16; 2],
|
/// pub color: [f32; 3],
|
||||||
/// color: [u8; 4],
|
|
||||||
/// }
|
/// }
|
||||||
/// unsafe impl Zeroable for Vertex {}
|
/// // if the type impls Default the macro can make its own default instance.
|
||||||
///
|
/// assert_eq!(offset_of!(Vertex, loc), 0);
|
||||||
/// let pos = offset_of!(Zeroable::zeroed(), Vertex, pos);
|
/// assert_eq!(offset_of!(Vertex, color), 12);
|
||||||
/// let uv = offset_of!(Zeroable::zeroed(), Vertex, uv);
|
|
||||||
/// let color = offset_of!(Zeroable::zeroed(), Vertex, color);
|
|
||||||
///
|
|
||||||
/// assert_eq!(pos, 0);
|
|
||||||
/// assert_eq!(uv, 8);
|
|
||||||
/// assert_eq!(color, 12);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ### Use with other types
|
|
||||||
///
|
|
||||||
/// More esoteric uses are possible too, including with types generally not safe
|
|
||||||
/// to otherwise use with bytemuck. `Strings`, `Vec`s, etc.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// #[derive(Default)]
|
|
||||||
/// struct Foo {
|
|
||||||
/// a: u8,
|
|
||||||
/// b: &'static str,
|
|
||||||
/// c: i32,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let a_offset = bytemuck::offset_of!(Default::default(), Foo, a);
|
|
||||||
/// let b_offset = bytemuck::offset_of!(Default::default(), Foo, b);
|
|
||||||
/// let c_offset = bytemuck::offset_of!(Default::default(), Foo, c);
|
|
||||||
///
|
|
||||||
/// assert_ne!(a_offset, b_offset);
|
|
||||||
/// assert_ne!(b_offset, c_offset);
|
|
||||||
/// // We can't check against hardcoded values for a repr(Rust) type,
|
|
||||||
/// // but prove to ourself this way.
|
|
||||||
///
|
|
||||||
/// let foo = Foo::default();
|
|
||||||
/// // Note: offsets are in bytes.
|
|
||||||
/// let as_bytes = &foo as *const _ as *const u8;
|
|
||||||
///
|
|
||||||
/// // we're using wrapping_offset here becasue it's not worth
|
|
||||||
/// // the unsafe block, but it would be valid to use `add` instead,
|
|
||||||
/// // as it cannot overflow.
|
|
||||||
/// assert_eq!(&foo.a as *const _ as usize, as_bytes.wrapping_add(a_offset) as usize);
|
|
||||||
/// assert_eq!(&foo.b as *const _ as usize, as_bytes.wrapping_add(b_offset) as usize);
|
|
||||||
/// assert_eq!(&foo.c as *const _ as usize, as_bytes.wrapping_add(c_offset) as usize);
|
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! offset_of {
|
macro_rules! offset_of {
|
||||||
@ -100,4 +79,7 @@ macro_rules! offset_of {
|
|||||||
assert!(result <= $crate::__core::mem::size_of::<$Type>());
|
assert!(result <= $crate::__core::mem::size_of::<$Type>());
|
||||||
result
|
result
|
||||||
}};
|
}};
|
||||||
|
($Type:path, $field:tt) => {{
|
||||||
|
$crate::offset_of!(<$Type as Default>::default(), $Type, $field)
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
59
tests/offset_of_tests.rs
Normal file
59
tests/offset_of_tests.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use bytemuck::{offset_of, Zeroable};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_offset_of_vertex() {
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex {
|
||||||
|
pos: [f32; 2],
|
||||||
|
uv: [u16; 2],
|
||||||
|
color: [u8; 4],
|
||||||
|
}
|
||||||
|
unsafe impl Zeroable for Vertex {}
|
||||||
|
|
||||||
|
let pos = offset_of!(Zeroable::zeroed(), Vertex, pos);
|
||||||
|
let uv = offset_of!(Zeroable::zeroed(), Vertex, uv);
|
||||||
|
let color = offset_of!(Zeroable::zeroed(), Vertex, color);
|
||||||
|
|
||||||
|
assert_eq!(pos, 0);
|
||||||
|
assert_eq!(uv, 8);
|
||||||
|
assert_eq!(color, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_offset_of_foo() {
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Foo {
|
||||||
|
a: u8,
|
||||||
|
b: &'static str,
|
||||||
|
c: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let a_offset = offset_of!(Default::default(), Foo, a);
|
||||||
|
let b_offset = offset_of!(Default::default(), Foo, b);
|
||||||
|
let c_offset = offset_of!(Default::default(), Foo, c);
|
||||||
|
|
||||||
|
assert_ne!(a_offset, b_offset);
|
||||||
|
assert_ne!(b_offset, c_offset);
|
||||||
|
// We can't check against hardcoded values for a repr(Rust) type,
|
||||||
|
// but prove to ourself this way.
|
||||||
|
|
||||||
|
let foo = Foo::default();
|
||||||
|
// Note: offsets are in bytes.
|
||||||
|
let as_bytes = &foo as *const _ as *const u8;
|
||||||
|
|
||||||
|
// we're using wrapping_offset here because it's not worth
|
||||||
|
// the unsafe block, but it would be valid to use `add` instead,
|
||||||
|
// as it cannot overflow.
|
||||||
|
assert_eq!(
|
||||||
|
&foo.a as *const _ as usize,
|
||||||
|
as_bytes.wrapping_add(a_offset) as usize
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&foo.b as *const _ as usize,
|
||||||
|
as_bytes.wrapping_add(b_offset) as usize
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&foo.c as *const _ as usize,
|
||||||
|
as_bytes.wrapping_add(c_offset) as usize
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user