posix_memalign can be replaced with std::alloc::Global.alloc. memset can use slice::fill I think.
Yes. Note that even on Linux the glibc and musl libc may use different definitions for the functions. You should also probably add an compile_error!() if the target doesn't match what you expect.
(not sure if the usage of assume_init and as_mut_ptr are correct)
Given that libc appears to just be wrappers, is there any real benefit for me to remove libc as a dependency (which seems to just duplicate the work of writing bindings and be error prone).
Rust's allocators don't expose mprotect, though, and a JIT needs that.
I was about to ask you that! I don't think libc is costing you much, just a little bit of build time. And if your program as a whole uses many libraries, it's quite likely one of them will pull in libc anyway. So unless you have strong evidence to the contrary, I'd say it's not worth avoiding libc.
As far as I know mem::uninitialized() is fine when undef is a valid value for the type. MaybeUninit is one of the few types for which undef is a valid value.
While it is valid, I don't see why you need it. MaybeUninit is used to not perform an expensive but redundant initialization code. Here, you can just initialize the pointer with null.
Also, the usual pattern is to initialize the pointer, then shadow the MaybeUninit with the .assume_init()ed value, so you don't need multiple .assume_init(). For example:
let addr = unsafe {
let mut raw_addr = MaybeUninit::uninit();
libc::posix_memalign(raw_addr.as_mut_ptr(), G.page_size, size); // allocate aligned to page size
let raw_addr = raw_addr.assume_init();
libc::mprotect(raw_addr, size, libc::PROT_READ | libc::PROT_WRITE); // read write
libc::memset(raw_addr, 0xc3, size); // return addr everywhere
addr as *mut u8
}
I agree that in this particular case, we can just use std::ptr::null_mut();. I have been playing around with MaybeUninit as I'm trying to understand it's power/limitations in full.
That's not entirely correct, there are other types where undef is a valid value:
union MyType { // basically how `MaybeUninit` is actually defined
a: (),
b: &'static str,
}
MaybeUninit::<MyType>::uninit().assume_init(); // valid since MyType::a doesn't need any valid bytes
MaybeUninit::<[MaybeUninit<u8>; 5]>::uninit().assume_init(); // valid since every byte is wrapped in `MaybeUninit`
MaybeUninit::<()>::uninit().assume_init(); // valid since zero-size types have no bytes that could be uninit
// Important exception:
// MaybeUninit::<!>::uninit().assume_init(); // not valid even though `!` is a zero-sized type, it is uninhabited -- it's not valid to ever create a value of an uninhabited type through any means.