> In general, I would avoid exposing Rust subsystems to C. It is possible, of course, and it gives you the advantages of Rust in the implementation of the subsystem. However, by having to expose a C API, you would be losing most of the advantages of the richer type system, plus the guarantees that Rust bring as a language for the consumers of the subsystem.
This feels like putting the cart before the horse. "we shouldn't integrate Rust into the kernel in a modular sort of way cause it's not as optimal as more thorough type-aware integration."
Like, cross that bridge when you come to it, eh? The kernel is currently comprised of hundreds if not thousands of components, talking to each other via C API/ABIs. And talking to hardware is almost entirely ABI. That is how the kernel do.
Would we benefit from more type knowledge across boundaries? Absolutely, but this is hard (especially given how different the c and rust approaches to allocation are), and shouldn't stand in the way of progress.
I agree. I've migrated multiple large codebases from a weakly-typed language to a strongly-typed language, and in my experience this is the only way to do it. And it's also not as bad as the OP makes it sound: you get your stronger guarantees gradually, and that's fine.
One gotcha, though: a stronger type system encourages the removal of runtime checks. When doing a partial conversion, I've found it's best to leave any existing runtime checks in place, even if the type system makes them "redundant", because it doesn't really until the calling code has also been converted. Once it has, you can go back and remove them.
(Clarification that these codebases were obviously not as large as Linux, and I don't mean to imply that the job was anywhere near the same scale as this one would be, I just think some of the same lessons are broadly applicable)
Exposing a C API/ABI is also the only supported way of making separate Rust crates that can be truly interoperable beyond the constraints of a single rustc build. It's not a limit to high-level interfaces because one can always build those as higher-level layers within a single crate, and there are tools (bindgen and cbindgen) to make this workflow a bit easier.
What do kernel subsystem interfaces usually look like? Is there a lot of shared ownership of objects with complex lifetimes (i.e. not just allocated on startup and never freed). Do APIs often require consumers to check invariants themselves? If so then there could be a lot of safety lost at the interface.
The best situations for using Rust modules from C are simple APIs that allow the complexity and danger to be strongly encapsulated.
This feels like putting the cart before the horse. "we shouldn't integrate Rust into the kernel in a modular sort of way cause it's not as optimal as more thorough type-aware integration."
Like, cross that bridge when you come to it, eh? The kernel is currently comprised of hundreds if not thousands of components, talking to each other via C API/ABIs. And talking to hardware is almost entirely ABI. That is how the kernel do.
Would we benefit from more type knowledge across boundaries? Absolutely, but this is hard (especially given how different the c and rust approaches to allocation are), and shouldn't stand in the way of progress.