-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add core::mem::offset_of!
RFC
#3308
Conversation
Oh right, CC @rust-lang/wg-const-eval since this RFC allow using this macro during const-eval. |
I am personally very much in favor of something like this. I do wonder if the lang team should be roped into this? From looking at And maybe that's all okay. I don't know. I don't think I mind having this as a macro. But that it might be something the lang team wants to evaluate too. (And they might just say, "nah libs-api can deal with this." But they might not.) |
As a user, I'd prefer if the docs could say "this info isn't some goofy library calculation, this info is directly from the compiler's data structures, guaranteed accurate, guaranteed no UB, guaranteed no runtime cost." Which is to say, yeah, "language verb" is probably a better framing. |
One important thing is missing from the reference: the layout of For example, the reference explicitly states that the layout of I think the RFC should have some motivation for allowing Being a macro, One possibility which isn't considered in the RFC is to use special constants instead of an expression macro. Now, defining those constants manually is mentioned, and has many problems, but what if they are defined by a compiler macro? For example, there could be some // before expansion
#[derive(FieldOffsets)
struct Foo {
first: u32,
pub second: Vec<Bar>,
pub(crate) third: (u8, bool, usize),
}
//after expansion
impl Foo {
const FOO_FIRST_OFFSET: usize = 0;
pub const FOO_SECOND_OFFSET: usize = 8; // for example
pub(crate) const FOO_THIRD_OFFSET: usize = 32; // for example
} For better ergonomics, the constants may be accessible via some expression macro
This implementation is also very hard to provide in a library crate, which provides a stronger argument for including it into the |
I've always wanted this feature, but I'm wondering what the rational would be for not supporting |
Yeah, I didn't know what to tag this as. I'm certainly not opposed to roping in t-lang, and added the label.
The intention is that's how this would be implemented (that's what the bit at the end of the reference is intended to mean), but "no runtime cost" is kind of difficult to define in a way that still works in implementations that don't make as firm of a distinction between compile time and runtime. Anyway, this felt likely to bog down the implementation under bikeshedding as to what this means, and in practice "evaluates to a constant expression" should be sufficient. I also don't want to forbid a pure-library implementation, although I believe implementing it as a builtin macro is likely to be better for several reasons (error messages, compile-time performance, reliability, ...).
Hm, largely because it prevents a pure library implementation. That said, I don't really care about that, and perhaps with enough cleverness it would still be possible. |
@afetisov (your comments are long enough to get a separate reply)
The use cases for this are largely the same as the use cases for using More generally, it is a goal of this RFC to remove the reasons people have to implement
In several ways it is already fixed at compile time (size and alignment are, which limits the size of the overall structure). In other ways, it is fixed at runtime. Allowing use in const evaluation prevents an implementation from doing things like randomizing field offsets at startup, unless it also performs modifications to adjust My suspicion is that many implementations which can randomize field offsets at startup can recompute constant values (because they're likely interpreted), but it's plausible that there's some middle-ground. Either way, this is more or less what I'm getting at by the first item under "Drawbacks" -- previously this information was only available at runtime, and this allows accessing it at compile time.
This kind of optimization (and more aggressive ones, like SROA) would still be allowed in exactly the same cases they are currently allowed. If a pointer to an instance of a structure does not escape, the offsets of its field do not matter. This is true regardless of the
Are you sure? From my experience with the code in Perhaps someone familiar with compiler internals cares to weigh in?
I'll add it to the RFC as an alternative, but I am not a fan of macros that expand to items not present in the source (you'll note that none of the existing builtin
I don't think there's any reason a derive macro would have more information here than is available to a builtin macro. They're essentially the same thing, and have essentially the same limitations.
I don't think this is true -- if I'll try to update drawbacks/alternatives/motivation with this feedback. |
Yes, the layouts are fixed. However, the as-if rule applies anyway. If the compiler can prove that the user can't tell the difference either way, it's always free to do whatever it wants as long as the behavior of the program is correct.
|
The pointer may not be computed explicitly, but perhaps there is still some roundabout way to get it via unsafe code. Trying to bound the behaviour of unsafe code hits Rice's theorem fast, and I can't imagine what would a reasonable definition of UB for Unless the reference explicitly guarantees that a type is always represented in the same way in memory, I would feel very uneasy about exposing
If the layout stability within a single artifact is not guaranteed, then all such calculations are borderline UB (their results can not be used for any unsafe operations in any nontrivial case).
Thank you, but that doesn't carry much weight unless it passes an RFC and is specified in the Reference. For all I know, that's just the current implementation restriction.
I would like to see specific use cases. I can't imagine how field offsets would help with (de)serialization of types with unspecified layouts, you certainly can't just transmute a slice of incoming data. Nor can I imagine how layout-unstable types could possibly be used in FFI, except as opaque types with no access to inner fields.
Well, that depends on the implementation and power of |
I would like to just put in the standard reminder that the Reference is a best effort documentation of how the compiler/language currently works, it's not actually an authority. If the reference and the compiler conflict about a point, it's just as likely that the reference will be updated as the compiler will be. |
Fortunately, the language doesn't have to worry about this. People writing compiler optimizations will be responsible for proving that the results of the optimization are not observable.
I don't really know how to respond to this besides saying that this is what it means for a type to have a layout. If the layout is not fixed, then it is not a property of the type, but a property of some other thing |
I got one: With the GPU you often have to send structured data and the way you do it is basically sending a bunch of bytes along with a description of what the fields of each field within each struct is. Details vary by API so I don't want to get into it too heavily, but you'd tell the API something like "the 0th field is an [f32;2] at offset 0, the 1th field is an [f32;3] at offset 8" or whatever, and then the GPU would read your buffer that way. |
I guess my question can be rephrased as "is it guaranteed that |
For something to have an unspecified layout it by definition must have a layout |
Well of course it has a layout. But the question is if there might be situations in the future where the compiler might want to optimize the layout based on the particular case where the variable is used, just like how a struct might never be on the stack or heap at all and instead be represented entirely in registers. Similar optimizations could be possible where the struct is sorted in a particular way in one function and differently in another. Stabilizing this macro for |
This macro is already writable with stable Rust, with 2 library implementations of varying QoL. A compiler that wishes to reorder structs (in a way as described above) must already ensure that such reordering does not impact the current library implementations. (This RFC, as written, allows a library-only implementation, even if that might not be the preferred route). |
You certainly can write the macro in stable Rust, but you still need unsafe to actually use that offset. So there's still a possibility that this offset is actually unsound to use then on a repr(Rust) type. However it's possibly indeed too late to allow such an optimization as people are probably relying on it enough already to break that. |
I've had a crate which wraps the concept of a field offset in a safe API which has existed for 6 years 😛 |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
@llogiq Would you be able to share your hacky solution? |
Sure! Look no further. Note that it involves |
@llogiq Nice! It's a const fn too. Thank you. I had assumed that either of these simpler mechanisms would yield the correct result:
I imagine this is naive, but I'm not sure what I'm missing that complicates it. And is there any solution for e.g. |
This method obviously doesn't work for unsized |
Enums can't have unsized fields... |
This comment was marked as resolved.
This comment was marked as resolved.
Also if enums are allowed to have unsized types at some point, a whole lot of |
@overlookmotel Btw. the complication is layout randomization. In fact I'm not even sure my hack works in the face of that. |
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue rust-lang#106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue #106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue #106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue #106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue #106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue #106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
Add offset_of! macro (RFC 3308) Implements rust-lang/rfcs#3308 (tracking issue #106655) by adding the built in macro `core::mem::offset_of`. Two of the future possibilities are also implemented: * Nested field accesses (without array indexing) * DST support (for `Sized` fields) I wrote this a few months ago, before the RFC merged. Now that it's merged, I decided to rebase and finish it. cc `@thomcc` (RFC author)
I started this a jillion years ago, and have been meaning to clean it up and submit it for a long time. The intention is to remove most (ideally all) need for hardcoding offsets, computing offsets manually, or using
{memoffset,bytemuck}::offset_of!
.CC @JakobDegen, @saethlin (since they provided some amount of input on this during RustConf)
Rendered