5 ms·
Calling C from Rust can be quite simple. You just declare the external function and call it. For example, straight out of the Rust book https://doc.rust-lang.or
by lambda 2y ago
Calling C from Rust can be quite simple. You just declare the external function and call it. For example, straight out of the Rust book https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#using-extern-functions-to-call-external-code https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#usin... :
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
Now, if you have a complex library and don't want to write all of the declarations by hand, you can use a tool like bindgen to automatically generate those extern declarations from a C header file: https://github.com/rust-lang/rust-bindgen https://github.com/rust-lang/rust-bindgen
There's an argument to be made that something like bindgen could be included in Rust, not requiring a third party dependency and setting up build.rs to invoke it, but that's not really the issue at hand in this article.
The issue is not the low-level bindings, but higher level wrappers that are more idiomatic in Rust. There's no way you're going to be able to have a general tool that can automatically do that from arbitrary C code.
- frankjr 2y agoThere's also cbindgen for going the other way around. https://github.com/mozilla/cbindgen https://github.com/mozilla/cbindgen
- varjag 2y agoThat's not really "simple", it's on par with C FFI in about any other language (except C++), with same drawbacks.
- gizmo686 2y ago... And? Most languages make C interop simple.
- varjag 2y agoThey quickly become unwieldy on non-trivial APIs, with hundreds of definitions across dozens of files and with macros to boot. Naturally people would still get the job done but it's beyond simple.
- mcronce 2y agoThat's what bindgen is for, as was mentioned in the original comment you replied to.
- varjag 2y agoHow well does it handle preprocessor macros in APIs?
- marshray 2y agoI have used it successfully against header files for Win32 COM interfaces generated from IDL which include major parts of the infamous "windows.h". Almost every type is a macro. This is an extremely well-understood space. Just open the docs and do it.
- varjag 2y agoNot types, functions. Where the macro is essentially a forward declaration but the implementation is deep inside the code and is not exposed via headers.
- commodoreboxer 2y agoIt's on par with C++, too. In C++ you need an `extern "C"`, because C++ linkage isn't guaranteed to be the same as C linkage. You can get away with wrapping that around it in a preprocessor conditional, but that's not all that much easier than Rust's bindgen. A lot of C to C++ interop is actually done wrong without knowing it. Throwing a C++ static function as a callback into a C function usually works, but it's not technically correct because the linkage isn't guaranteed to be the same without an extern "C". In practice, it usually is the same, but this is implementation-defined, and C++ could use a different calling convention from C (e.g. cdecl vs fastcall vs stdcall. The Borland C++ compiler uses fastcall by default for C++ functions, which will make them illegal callbacks for C functions). The major difference between Objective-C and C++'s C interop and other languages is the lack of the preprocessor. Macros will just work because they use the same preprocessor. That's really not easy to paper over in other languages that can't speak the C preprocessor.
- spacechild1 2y agoI think you're confusing some terms here. > In C++ you need an `extern "C"`, because C++ linkage isn't guaranteed to be the same as C linkage. `extern "C"` has nothing to do with linkage, all it does is disable namemangling, so you get the same symbol name as with a C compiler. > Throwing a C++ static function as a callback into a C function usually works, but it's not technically correct because the linkage isn't guaranteed to be the same without an extern "C". Again, linkage is not relevant here. Your C++ callbacks don't have to be declared as extern "C" either, because the symbol name doesn't matter. As you noted correctly, the calling conventions must match, but in practice this only matters on x86 Windows. (One notable example is passing callbacks to Win32 API functions, which use `stdcall` by default.) Fortunately, x86_64 and ARM did away with this madness and only have a single calling convention (per platform).
- LegibleCrimson2 2y ago> `extern "C"` has nothing to do with linkage, all it does is disable namemangling, so you get the same symbol name as with a C compiler. extern "C" also ensures that the C calling convention is used, which is relevant for callbacks. It's not just name mangling. This is the reason that extern "C" static functions exist. You can actually overload a C++ function by extern "C" vs extern "C++", and it will dispatch it appropriately based on whether the passed in function is declared with C or C++ linkage. And I'm not sure the terms are confused, because that's how most documentation refers to it: https://learn.microsoft.com/en-us/cpp/cpp/extern-cpp?view=msvc-170#extern-c-and-extern-c-function-declarations https://learn.microsoft.com/en-us/cpp/cpp/extern-cpp?view=ms... > In C++, when used with a string, extern specifies that the linkage conventions of another language are being used for the declarator(s). C functions and data can be accessed only if they're previously declared as having C linkage. However, they must be defined in a separately compiled translation unit. And https://en.cppreference.com/w/cpp/language/language_linkage https://en.cppreference.com/w/cpp/language/language_linkage The post you're replying to had it completely right. extern "C" is entirely about linkage, which includes calling convention and name mangling. > As you noted correctly, the calling conventions must match, but in practice this only matters on x86 Windows. Or if you want your program to actually be correct, instead of just incidentally working for most common cases, including on future systems. If you're passing a callback to a C function from C++, it's wrong unless the callback is declared extern "C".
- kelnos 2y agoHow is that not simple? You just declare the function and then call it. I find it hard to imagine how it could be any more simple than that.
- varjag 2y agoNow imagine a hundred or two functions, structures and callbacks, some of them exposed only as CPP macros over internal implementation. PJSIP low level API is one example.
- lambda 2y agoBut... that's what bindgen is for. Which I mentioned. I said it "can be quite simple"; for simple use cases, just using extern and translating the declarations by hand is perfectly viable. For more complex cases, you use bindgen.
- varjag 2y agoBindings generators exist in most other languages with same limitations. I would love to see how bindgen would handle a function call defined as a preprocessor macro that I mentioned. Because most likely it won't.
- googh 2y agoCan someone shed some light on why the parent comment (by varjag) is downvoted?
- jacobgorm 2y agoPassing integers around is easy, sharing structs or strings and context pointers for use in callbacks crossing the language barrier etc is typically much harder.
- Someone 2y agoFor rust code calling C, sharing structs is doable with #[repr(C)]. See https://doc.rust-lang.org/reference/type-layout.html#reprc-structs https://doc.rust-lang.org/reference/type-layout.html#reprc-s... (Nitpick: I don’t think it technically is correct to call this “The C representation”, as strict layout in C depends on the C compiler/ABI. I wouldn’t trust this to be good enough for serializing data between 32-bit and 64-bit systems, for example. For calling code on the same system, it’s good enough, though)