Can we get rid of the unsafe wrapper of OS functions?


#1

I am translating several of my Windows programs written in C into rust.There was heavy use of Windows API functions in the programs. As an example, I need to call the CreateWindowExA function. However, since all API functions are considered by Rust to be unsafe, I need to place the function call in a wrapper:

unsafe { hwnd = CreateWindowExA(...); }

But this is burdensome as there are so many API calls in my programs. Is it possible that we declare an external function unsafe in its declaration? For example, can we write something like

pub unsafe fn CreateWindowExA(
dwExStyle: DWORD,
lpClassName: LPCSTR,
lpWindowName: LPCSTR,
dwStyle: DWORD,
x: c_int,
y: c_int,
nWidth: c_int,
nHeight: c_int,
hWndParent: HWND,
hMenu: HMENU,
hInstance: HINSTANCE,
lpParam: LPVOID,
) -> HWND;

I know this is illegal for now, but will Rust adopt it in the future? So that we can get rid of the unsafe wrapper in the caller function body?


Ambiguity of "unsafe" causes confusion in understanding
#2

Actually, “unsafe” when applied to a function declares that calling the function is “unsafe”. That is why you have to wrap calls to “CreateWindowExA” in an “unsafe” block. It is because there are invariants, that you, as the applicaiton autho must ensure are met before calling that function in order to call it safely.

That’s what “unsafe { … }” means. It is telling the compiler, I’m about to do this unsafe thing, in this case, calling a function declared “unsafe”, but, don’t worry, I’ve ensured that the invariants have been met.

You need to consult the Rust docs of the crate that delcares “CreateWindowExA” as an “unsafe” function. The Rust docs should have a “Safety” documentation that explains the invariants that you must ensure are met in order to safely call the function. If it does not, that is bug in the documentation and should be reported as a bug to that crate.

You should have a look at: https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html
and https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html

One of the most important sentences from that for you to get your head around is:

The unsafe keyword has two uses: to declare the existence of contracts the compiler can’t check, and to declare that a programmer has checked that these contracts have been upheld.


#3

The reason this function is unsafe is because we’re calling functions directly out of a DLL from C code. There are several things which could go wrong and the compiler has no way to prevent, ranging from using the wrong parameters (e.g. the declaration is CreateWindowExA(int x, int y) but we call it as CreateWindowExA(double x)) to the fact that we’re calling arbitrary C code which doesn’t necessarily satisfy the same safety constraints rustc requires.

All FFI functions (i.e. when you call into non-Rust code or a DLL) require you to use unsafe because, by their very nature, it’s not possible for the compiler to prove the code is actually safe.

unsafe { } blocks aren’t like a lint which you can just turn off. They are a guarantee by the programmer that the code you are writing is valid and all constraints on the system are upheld.

Unfortunately it’s not really correct to just slap a wrapper on it either, seeing as that “wrapper” is actually meant to be making sure your program does things in a memory/thread-safe way. Wrapping unsafe calls with a trivial wrapper subverts the language’s safety guarantees.