-
Notifications
You must be signed in to change notification settings - Fork 534
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
Feature request: Represent void*
types as &[MaybeUninit<u8>]
#2065
Comments
Happy to consider a compromise solution. It's certainly useful when it works. 😊 |
In #2044 I think the chief complaint is this:
Is it possible to make a sound API for that? Are my little one-liners even sound? Within the confines of safe Rust, any I'm not sure about arrays of objects though; I haven't run into them in my small window manager yet. |
@rylev What are the sound options for code that's something like: let mut file_basic_info: FILE_BASIC_INFO = Default::default();
SetFileInformationByHandle(
handle,
FileBasicInfo,
as_u8_slice(&file_basic_info)); |
Unfortunately, that's not always true. |
Does Is passing in a manually created pointer+size defined behavior? |
Yeah, my answer was a bit imprecise.
The data the slice points to (which is the same rationale as the reference case) is invalid because it contains an integer obtained from uninitialized memory. There's related discussion on the |
Do slices contain the data, or point to the data? I always thought they were a pointer+size, which meant the subscript operation is a pointer offset operation, which would be UB to access the padding bytes, but not UB at long as they're never read or written. Since the Win32 function isn't modifying the padding bytes, it's modifying the initialized data bytes for the struct in question, it could be ok. I'm still curious if there's any UB with passing in a Rust pointer. That seems more dangerous to me, even if there's no UB. I'm not knowledgeable about this stuff at all, I'm really just trying to understand when UB does occur and when it could occur but only if you take that next step. Thanks for your help and patience explaining it to me. |
The slice is a pointer plus a length, but the docs for |
I believe performing a conversion from
|
The documentation for There are nightly APIs, MaybeUninit::as_bytes() and MaybeUninit::as_bytes_mut(), that do something very close to what I wrote above, and someone in the tracking issue at least thought about its effect on padding bytes. Windows-rs could use those functions (well, custom equivalents until they're stabilized) to convert a let mut file_basic_info: MaybeUninit<FILE_BASIC_INFO> = MaybeUninit::new(FILE_BASIC_INFO::default());
SetFileInformationByHandle(
handle,
FileBasicInfo,
file_basic_info.as_bytes_mut());
let mut file_basic_info = file_basic_info.assume_init(); So now the new request in this Issue is to use |
void*
types as &[u8]
againvoid*
types as ~&[u8]
again~ &[MaybeUninit<u8>]
void*
types as ~&[u8]
again~ &[MaybeUninit<u8>]
void*
types as &[MaybeUninit<u8>]
Sounds like this has potential but depends on experimental APIs, so we may have to postpone until those APIs are stabilized. |
Nah, it's an easy conversion to write once you know the proper incantation: trait AsVoidPointer
where
Self: Sized,
{
unsafe fn as_const_void_pointer(&self) -> &[core::mem::MaybeUninit<u8>] {
std::slice::from_raw_parts(
self as *const Self as *const core::mem::MaybeUninit<u8>,
std::mem::size_of::<Self>(),
)
}
unsafe fn as_mut_void_pointer(&mut self) -> &mut [core::mem::MaybeUninit<u8>] {
std::slice::from_raw_parts_mut(
self as *mut Self as *mut core::mem::MaybeUninit<u8>,
std::mem::size_of::<Self>(),
)
}
}
impl AsVoidPointer for RECT {}
impl AsVoidPointer for FILE_BASIC_INFO {} I'm not sure how a blanket impl like this example will impact compile times though. Bike shedding the name of the trait and functions would be the hardest part I think, because they're genuinely important. |
Sure, but I'd rather not introduce a temporary fix when something may be on its way from the std library. Rather spend the effort getting it stabilized. |
The // Passing in a parameter is easy-peasy with the trait function defined above
//
pub fn dwm_get_window_attribute_extended_frame_bounds2(
hwnd: HWND,
rect: &mut RECT,
) -> windows::core::Result<()> {
unsafe {
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, rect.as_void_pointer())
}
}
// Using the MaybeUninit<RECT> to get a &[MaybeUninit<u8>] is cumbersome,
// and requires two extra copies of the data I think
//
pub fn dwm_get_window_attribute_extended_frame_bounds3(
hwnd: HWND,
rect: &mut RECT,
) -> windows::core::Result<()> {
unsafe {
let r = MaybeUninit::new(rect.clone());
let result = DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, r.as_bytes_mut());
*rect = r.assume_init();
result
}
} |
I'm definitely not a Windows API expert so I could very well be mistaken, but I think writing the code like this would be more idiomatic as well as resolve your concern about the double copying: pub fn dwm_get_window_attribute_extended_frame_bounds3(
hwnd: HWND
) -> windows::core::Result<RECT> {
unsafe {
let r = MaybeUninit::<RECT>::uninit();
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, r.as_bytes_mut())?;
Ok(r.assume_init())
}
} How do you feel about that? |
This is the vastly superior strategy for my toy sample, yes. But now the pub fn dwm_get_window_attribute_extended_frame_bounds3(
hwnd: HWND
) -> windows::core::Result<RECT> {
unsafe {
let r = RECT::default();
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, r.as_void_pointer())?;
Ok(r)
}
} |
Thanks for the feedback! This is definitely something to keep in mind as we continue to improve @wesleywiser and I were chatting about this. While this is an interesting problem, it is also one of the grey areas of writing unsafe code that is still being figured out and best to be overly cautious for now. So, I'm reluctant to jump ahead with a solution when it works just fine right now. We can let this concept bake in the language and std library before jumping in with extra support. I'm going to close this issue for now and we can revisit in future, but feel free to keep the conversation going. |
Yeah, passing in arbitrary objects as |
Motivation
In 0.40.0
const void*
was represented as a&[u8]
and avoid *
was represented as amut& [u8]
. This was an improvement over using separatecore::ffi::c_void
andcbattribute
parameters where you could pass in the wrong size for the buffer. That might be a feature in some cases.With a couple of helper functions the single parameter was easy to use:
If the metadata for the Win32 parameter is
optional
, the Rust projection should be anOption<&[u8]>
.If the Rust buffer parameter is
None
, then0
should be passed to thecbattribute
Win32 function parameter.Drawbacks
It requires
unsafe
to convert a&T
to a&[u8]
. It also requiresunsafe
to convert it to ac_void
and get the size of the object, which might be more error prone.Rationale and alternatives
Current use of
c_void
and a separate size parameter isn't very Rusty and allows for simple copy/paste mistakes.Edit: The padding bytes in an object are uninitialized, so it's not safe to convert the object into a slice. A
& [MaybeUninit<u8>]
is safe however.Additional context
0.40.0 had this feature and it worked quite well I thought.
The text was updated successfully, but these errors were encountered: