And C++ would not even exist if it couldn't directly consume C :D
Posts by sneed
16 publicly visible posts • joined 27 Apr 2022
Is it time to retire C and C++ for Rust in new programs?
Re: Typescript would not even exist if it couldn't directly consume JavaScript.
Would you say that C++ is a "total waste of time" as well then?
There are many benefits from using TypeScript in a project over JavaScript. Type safety is no joke. A whole class of runtime errors in JavaScript is eliminated by TypeScript. That's half of the point in it.
You are confusing safety with program correctness. Safety is for security purposes: a buffer overflow can allow an attacker to read or write to arbitrary memory. A real world example of this is the Heartbleed vulnerability, where a buffer overflow from unchecked array indexing opened up a security vulnerability that could allow attackers to read sensitive data such as private keys, session cookies, passwords, etc.
Lifetime of objects is outside the scope of binding generators (I think that's your point, though). All they do is expose callable, `unsafe`, FFI functions that you can call in your Rust code. It doesn't automatically convert pointers into references or anything, that's impossible and up to you to do.
Generally, the idiomatic way to handle all of this is to create a "-sys" crate. This crate will be your raw FFI bindings. Then, you create a new crate where you create safe abstractions around the unsafe bindings.
For example, say we have a C library that exposes 3 functions: a function that mallocs an object and returns a void* pointer to it (`init_it`), a function that frees that object (`free_it`), and a function that... I don't know... sends the object as an email to Bill Gates (`email_it`). I'll generate bindings (or manually write them myself, which in this case would be 25 lines of code or so - bindings to the functions and a bit of code in the build script that compiles & statically links the C library) to those 3 functions in my "-sys" crate.
Then, I can build a safe Rust API around these C functions. I'll create a struct whose constructor calls `init_it`, and stores the returned void* pointer in a struct field. Then, I can implement a method that calls the `email_it` function. Finally, I can implement the `Drop` trait on the struct, which defines what to do when the object's lifetime ends. In this case, it will just call `free_it`.
This is basically how almost all of Rust's standard library interfaces with the operating system. File is just a struct that wraps a HANDLE on Windows, or a file descriptor on UNIX, and its Drop implementation closes the handle or fd.
No one out there is bothering to spend loads of manual hours doing this unless there's a lot of demand for it. The open source community is nice for this, and we have a lot of these safe "C library interfacing" crates already available. For example, OpenSSL, Tesseract OCR, libpng, etc. all have safe Rust libraries that will even compile and link these libraries for you.
Anyway, handling lifetimes isn't a problem exclusive to generated bindings. If you use a C library in a C project, you are still going to have to handle lifetimes. And when it comes to RAII, luckily, in Rust, you can just use the cxx crate to wrap unique_ptr and friends. I'm sure there are similar libraries to help with interfacing with GC'd languages.
Well, I would estimate maybe ~10% of my time in these projects are related to bindings. I find that Rust saves me a ton of time when actually developing the software, so the trade off of having to do a little extra work with bindings breaks-even.
C bindings are a breeze; I've never encountered any issues with bindgen for C libraries. It's really, very, surprisingly good. Sometimes it takes a little tweaking to get it how you like it (e.g. choosing your desired style of how enum "bindings" are generated), but these are just a single switch in the build script when configuring the generated bindings.
C++ bindings - not so easy. It has no stable ABI, so that's where the "little extra work" comes in. This usually just involves exposing stuff I need under `extern "C"` functions. This is a really silly idea for projects where you're going to be interfacing with A LOT of C++ code, and you may as well either just use C++ directly, use some new kid on the block like Carbon, or give the Rust cxx crate a try, which can do a lot of heavy lifting for you.
It's worth mentioning that none of this would be easy without Cargo. In build.rs, you can use the cc crate to compile and statically link in C code to your program in a criminally little number of lines of code. I'm currently working on a project that does this with C, C++ and Objective C code, so my src/ folder is pretty diverse. To quantify, the amount of Rust in this project is 71%.
Re: C/C++ - really?
I am well aware! I am just talking specifically in the context of unwinding the stack to throw an error. Rust does not need to do this, is my point. (It does however do this for panics, which are intended to be unrecoverable errors. And you of course can capture a backtrace manually for logging purposes, too.)
> TypeScript's most powerful feature is that it can directly consume JavaScript code.
Doesn't sound nearly as impressive when you take your bias out of it, does it?
> The bindings created tend to rot because they cover the entire API so are very fragile to breakage in a tiny area you might never even use.
I'm not really sure what you meant by this, but generated bindings are created at build time and 99.999% of the time never included in source control. Ergo, they can easily adapt to environmental differences and changes to the code they're binding to, and regenerate in response to these changes automagically.
I would recommend reading the bindgen user guide to learn more: https://rust-lang.github.io/rust-bindgen/
Re: C/C++ - really?
Okay, I'll bite RE error handling. Rust explicitly DIScourages a coding style where errors are ignored. You are forced to handle errors to get the "Ok" value out of a Result. And most boilerplate code from handling errors is sugared by the "?" operator: https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html
In contrast with C++ exceptions, I think something worth mentioning is that Rust's error handling has no need for stack unwinding. "Unwinding" in the context of calling destructors during error handling is already handled by the Rust compiler's Drop magic. https://doc.rust-lang.org/reference/destructors.html#drop-scopes
Re: Complier support
Safety: Ferrous Systems have been developing Ferrocene for this: https://ferrous-systems.com/ferrocene/
Microcontrollers: I'm no expert on this, but Rust is gradually improving its support here. Supported targets are explained in detail here: https://doc.rust-lang.org/nightly/rustc/platform-support.html
I think it's also worth mentioning that a GCC frontend for Rust is in the works, too: https://github.com/Rust-GCC/gccrs
Re: C++ and memory safety
Could you elaborate on what you mean? Rust wraps handles in safe abstractions that even perform cleanup (e.g. closing handles) when they go out of scope.
https://doc.rust-lang.org/std/os/unix/io/trait.AsRawFd.html#implementors
https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#implementors
Rust is eating into our systems, and it's a good thing
Re: Hear hear!
This one confused me a bit, you despise "=>" because it's "tedious" to type, and "you need to type it all the time", but are totally fine with tediously typing out "function" and "public" - all the time?
Chopping these keywords down makes them less tedious and isn't any less clear: Hmans r vry gd at fguring out wht wrds with mssng lettrs mean, and this time you're not even having to read a sentence. Maybe this would have some merit if Rust went gung-ho and chopped "break" down to "brk" (for example), but, it doesn't: https://doc.rust-lang.org/reference/keywords.html
BTW, "=>" doesn't even make it into the top 10 most commonly used syntax in Rust, and nor does "->", which is what I think you actually might be talking about. https://twitter.com/davidtolnay/status/1534955471714406402/photo/1
Heresy: Hare programming language an alternative to C
Re: I think they need to jusify their claims far better than they have done so far
fn makes the job of parsing much easier. C and C++ are notoriously difficult to parse and the lack of any concrete "markers" for function declarations is a big contributing factor to that. Any language whose goal is to be easy to parse will have a fn or similar marker for function declarations. See Lua and Rust (whose ease of parsing makes for writing declarative and procedural macros a very nice experience)
I don't see the point in complaining over a 2-letter keyword that makes the language itself easier to develop.