Comment by Certhas
10 months ago
It's also not truthful because many of the Rust maintainers are long time C contributors.
Marcan also linked to this resignation of a Rust Maintainer:
https://lore.kernel.org/lkml/20240828211117.9422-1-wedsonaf@...
which references this fantastic exchange:
https://www.youtube.com/watch?v=WiPp9YEBV0Q&t=1529s
I am not a C person, or a kernel level person, I just watch this from the sideline to learn something every now and then (and for the drama). But this exchange is really stunning to me. It seems so blatantly obvious to me that systematically documenting (in code!) and automatically checking semantic information that is required to correctly use an API is a massive win. But I have encountered this type of resistance (by very smart developers building large systems) in my own much smaller and more trivial context. To some degree, the approach seems to be: "If I never write down what I mean precisely, I won't have to explain why I changed things." A more charitable reading of the resistance is: Adding a new place where the semantics are written down (code, documentation and now type system) gives one more way in which they can be out of sync or subtly inconsistent or overly restrictive.
But yeah, my intuitive reaction to the snippet above is just incredulity at the extreme resistance to precisely encoding your assumptions.
Your charitable reading is too charitable. One of the benefits of using types to help guarantee properties of programs (e.g. invariants) is that types do not get out of sync with the code, because they are part of the code, unlike documentation. The language implementation (e.g. the compiler) automatically checks that the types continue to match the rest of the code, in order to catch problems as early as possible.
I'm not a kernel developer, and never done anything of the sorts either. But, I think the argument is that if they have two versions of something (the C version + the Rust bindings), the logic/behavior/"semantics" of the C version would need to be encoded into the Rust types, and if a C-only developer changes the C version only, how are they supposed to proceed with updating the Rust bindings if they don't want to write Rust?
At least that's my understanding from the outside, someone please do correct me if wrong.
That was a large part of the disagreement.
Rust developers were saying it would be their job to do this. But then someone said Linus rejected something because it broke Rust. GKH backed the Rust developers and said that was an exception not a rule, but didn't know Linus' stance for sure.
Then Linus chimes in because of one of Hector's replies, but at the time of my reading did not clarify what his actual stance is here.
9 replies →
> how are they supposed to proceed with updating the Rust bindings if they don't want to write Rust?
If I've interpreted it correctly (and probably not, given the arguments), Linus won't accept merge requests if they break the Rust code, so the maintainer would need to reach out to the Rust for Linux (or someone else) to fix it if they didn't want to themselves.
And some lead maintainers don't want to have to do that, so said no Rust in their subsystem.
Which is a moot point because the agreement right now is that Rust code is allowed to break, so the C developer in question can just ignore Rust, and a Rust person will take care of it for them.
5 replies →
Yes, but generic code complicates the picture. The things I saw were like: The documentation says you need a number but actually all you need is for the + operator to be defined. So if your interface only accepts numbers it is unnecessarily restrictive.
Conversely some codepath might use * but that is not in the interface, so your generic code works for numbers but fails for other types that should work.
> Yes, but generic code complicates the picture. The things I saw were like: The documentation says you need a number but actually all you need is for the + operator to be defined. So if your interface only accepts numbers it is unnecessarily restrictive.
if you really need a number, why not use a type specifically aligned to that (something like f32|f64|i32|i64 etc...) instead of relying on + operator definition?
> Conversely some codepath might use * but that is not in the interface, so your generic code works for numbers but fails for other types that should work.
do we agree that if it's not in the interface you are not supposed to use it? conversely if you want to use it, the interface has to be extended?
1 reply →
It's practically impossible to document all your assumptions in the type system. Attempting to do so results in code that is harder to read and write.
You have a choice between code that statically asserts all assumptions in the type system but doesn't exist, is slow, or a pain to work with, and code that is beautiful, obvious, performant, but does contain the occasional bug.
I am not against static safety, but there are trade offs. And types are often not the best way to achieve static safety.
> And types are often not the best way to achieve static safety.
That’s a sort of weird statement to make without reference to any particular programming language. Types are an amazing way to achieve static safety.
The question of how much safety you can reasonably achieve using types varies wildly between languages. C’s types are pretty useless for lots of reasons - like the fact that all C pointers are nullable. But moving from C to C++ to Rust to Haskell to ADA gives you ever more compile time expressivity. That type expressivity directly translates into reduced bug density. I’ve been writing rust for years, and I’m still blown away by how often my code works correctly the first time I run it. Yesterday the typescript compiler (technically esbuild) caught an infinite loop in my code at compile time. Wow!
I’d agree that every language has a sweet spot. Most languages let you do backflips in your code to get a little more control at compile time at the expense of readability. For example, C has an endless list of obscure __compiler_directives that do all sorts of things. Rust has types like NonZeroUsize - which seem like a good idea until you try it out. It’s a good idea, but the ergonomics are horrible.
But types can - and will - take you incredibly far. Structs are a large part of what separates C from assembler. And types are what separates rust from C. Like sum types. Just amazing.
Encoding assumptions and invariants in the type system is a spectrum. Rust, by it's very nature, places you quite far along that spectrum immediately. One should consider if the correctness achieved by this is worth the extra work. However, if there is one place where correctness is paramount, surely it's the Linux Kernel.
> [..]Attempting to do so results in code that is harder to read and write.
> You have a choice between code that statically asserts all assumptions in the type system but doesn't exist, is slow, or a pain to work with, and code that is beautiful, obvious, performant, but does contain the occasional bug.
I don't think you are expressing objective truth, this is all rather subjective. I find code that encodes many assumptions in the type system beautiful and obvious. In part this is due to familiarity, of course something like this will seem inscrutable to someone who doesn't know Rust, in the same way that C looks inscrutable to someone who doesn't know any programming.
> Encoding assumptions and invariants in the type system is a spectrum. Rust, by it's very nature, places you quite far along that spectrum immediately.
Compared to, say, dependent type systems, Rust really isn't that far along. The Linux kernel has lots of static analyzers, and then auxiliary typedefs, Sparse, and sanitizers cover a significant area of checks in an ad-hoc way. All Rust does is formalize them and bring them together.
And getting Rust into the kernel slowly, subsystem by subsystem, means that the formalization process doesn't have to be disruptive and all-or-nothing.
But if the info is info the user of your code needs in order to interface correctly, the point that you can't document everything is moot. You already have to document this in the documentation anyways.
The particular C maintainers in discussion refused to provide even textual documentation.