Hacker News new | ask | show | jobs
by deathanatos 3074 days ago
I think the Rust is not how you should write such a code. Why not start with the struct, and cast to a void* or a char* when C code requires it? I.e., the buggy example becomes:

  #[derive(Copy, Clone, Debug)]
  #[repr(C)]
  struct Foo {
      a: i32,
      b: i32,
  }

  fn main() {
      let mut array = [Foo { a: 0x01010101i32, b: 0x01010101i32 }; 256];
      let foo = &mut array[0];
      foo.a += 1;
  }
The unsafe section isn't even required, and the effect is the same. And I don't think this violates the spirit of his example, either. Consider the author's first link to a real-world occurrence of this:

            let size = mem::size_of::<FILE_NAME_INFO>();
            let mut name_info_bytes = vec![0u8; size + MAX_PATH];
            let res = GetFileInformationByHandleEx(handle,
                                                FileNameInfo,
                                                &mut *name_info_bytes as *mut _ as *mut c_void,
                                                name_info_bytes.len() as u32);
This is again, IMO, the wrong way to do this. You should just cast a pointer to an instance of the FILE_NAME_INFO struct into a c_void; the structure will need to use #[repr(C)] and the code will still be unsafe due to the C FFI, but it will be correct (and a lot simpler). This is the same thing that you would do in C, were you to call this function:

  FILE_NAME_INFO file_name_info;
  GetFileInformationByHandleEx(
      handle,
      FileNameInfo,
      &file_name_info,
      sizeof(file_name_info),
  )
just in Rust.
2 comments

While the approach you suggest usually works well, it doesn't in this case: FILE_NAME_INFO[1] uses a "flexible array member"[2] (although not the C99 version of it), of requiring a dynamically sized character array in the struct's allocation, and writing directly to the memory after a struct instance. The 'WCHAR FileName[1];' field at the end of the struct is just a placeholder to allow easy access to that character array, the length 1 is a lie.

[1]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa3...

[2]: https://en.wikipedia.org/wiki/Flexible_array_member

Ugh. You're absolutely right. I never liked those even in C.

So, it seems like this is relative easy to do on the stack, which is how the example does it presently. See the link below to my attempt; the stack allocation is still all safe code, still a single line. However, I presume that one will want to also create one on the heap, especially since in the example the author poses it would be a rather large stack allocation, and one might — quite reasonably — put that on the heap.

My attempt is here: https://play.rust-lang.org/?gist=1c50b35941506316372da860cae...

Couldn't avoid the unsafe for that, but, I was able to get rid of the transmute call, and transmute is a function where the warning on the tin is "this function is not just unsafe, it is radioactive". But the amount of code required still felt a bit lacking.

It seems these are an area of active work[1][2] currently.

I think there is still definitely a valid point that the author is hitting — that encoding more information into the program can allow the compiler to catch more classes of errors. (This is, after all, the very logic that gave us Rust.)

[1]: https://github.com/rust-lang/rfcs/pull/1909

[2]: https://github.com/rust-lang/rust/issues/18806

The function in question takes a pointer to a variable sized buffer that only starts with a struct. So your alternative won't work (the declared size of the struct only has room for a single character of filename). And there are certainly instances of this pattern where you really need to choose the size of the buffer at run time.