Context: I'm aware the whole "unsafe
" portion of the language exists precisely to avoid all the issues that come into existence the moment one decides to venture into this level of detail and manually manage all the underlying size/alignment/lifetimes involved. To me, this is a learning exercise.
After dabbling in and out of the standard safety guarantees I came into a much closer contact with zero-guarantees, hands-on-memory, nail-or-crash type of code, such as:
fn main() {
let array: [u16; 1] = [1];
let tuple = array.cast::<(bool, bool)>();
println!("{:?}", tuple);
}
trait Castable<T: Copy> {
fn cast<X: Copy>(&self) -> X;
}
impl<T: Copy> Castable<T> for T {
fn cast<X: Copy>(&self) -> X {
unsafe { *(self as *const T as *const X) }
}
}
Without (significant) prior experience with C/C++ and their static/const/reinterpret_cast
- as well as virtually complete and utter (though, in retrospect, somewhat willfully blind) trust in "all unsafe
is bad" - something I might have taken on board long before realizing what unsafe
ops actually are - the code above "clicked" in a somewhat better/deeper level of understanding.
The underlying memory management mechanisms for the regular variables seem rather straightforward enough, so far. Yet I'm struggling quite a bit to wrap my head around the way functions are represented in it. Reading about stdcall
and the rest of calling conventions seems to have raised even more questions. Initialized registers? Stack/return pointers? Utter confusion.
Can someone write up the most basic example of a fn
, declared completely from scratch - which does absolutely nothing, as in fn test() {}
- yet allocated from ground up in pure lower level tuples / arrays / structs, showing what's going on beneath it all, when it comes to memory?
Or would that involved a great deal of platform-specific asm
declarations as well?