5 ms·
So I read about things being the ultimate evil. But what is your counter proposal to make the world a better place? Rewrite everything in Rust in one go? Not g
by Matthias247 6y ago
So I read about things being the ultimate evil. But what is your counter proposal to make the world a better place?
Rewrite everything in Rust in one go? Not going to happen.
The last teams I worked on all maintained a giant pile of C and C++ code. Which obviously sometimes is buggy, and sometimes might even exhibit memory safety issues. I’m looking for every opportunity to make things better and using Rust for to improve safety and reliability. However due to realities this isn’t going to happen at once.
So now we have 2 choices:
Option 1: use bindings and replace things step by step, knowing that we have still unsafe code (but less so!)
Option 2: Stick with C and C++
I think everyone agrees that option 1 is better than 2. But reading about it as „the ultimate evil“ makes me sound very uncomfortable.
- The_rationalist 6y agoI think everyone agrees that option 1 is better than 2. What? It seems pretty reasonable to be against it. Code clarity is the number one priority and the ffi are going to add cognitive overhead / clutter / boilerplate which is harmful.
- fluffything 6y ago> Rewrite everything in Rust in one go? No? Just do what every good Rust programmer does today instead? If I have a C++ function like this: // If a == nullptr the behavior is undefined void foo(int* a); the right way to provide a safe Rust API for it is to write: mod ffi { extern "C" { fn foo(a: *mut c_int); } } fn foo(a: *mut c_int) { assert!(!a.is_null()); unsafe { ffi::foo(a) } } The crates being discussed here generate: mod ffi { extern "C" { fn foo(a: *mut c_int); } } fn foo(a: *mut c_int) { unsafe { ffi::foo(a) } } instead (notice the missing assert), which is broken Rust code according to the Rust spec, because now `foo` will introduce undefined behavior into safe Rust every time its safely called with a null pointer. That is, doing this makes _all_ Rust safe code "unsafe", making the unsafe Rust keyword essentially meaningless, and negating any advantage that Rust has over C++ (memory safety, thread safety, lacks of segfaults, refactoring without introducing errors, etc.). So you end up with 2 languages in your codebase, + a lot of glue boilerplate, for very little win. If this is what you want, you are better off just sticking with C++ instead. What Firefox, Servo, and any other good C++ project that cannot/does not want to review all C++ APIs does is to just write: extern "C" { fn foo(a: *mut c_int); } instead. That's less code, and it is correct code. When a programmer needs to call foo, they need to write "unsafe { foo(a) }", and that often leads to them actually checking "foo"'s API docs, and explaining why the call is safe (maybe the assert is not needed because a cannot be null. The burden of doing the work is on the caller. Firefox and Servo heavily use `rust-bindgen`, which will automatically generate all these FFI wrappers with a correct ABIs for you. The main difference is that rust-bindgen does not attempt to falsely convey the idea that these APIs are safe to call.