rust-lang / libc

Raw bindings to platform APIs for Rust
https://docs.rs/libc
Apache License 2.0
2.09k stars 1.04k forks source link

Discussion about removing `extra_traits` in 1.0 #3880

Open tgross35 opened 2 months ago

tgross35 commented 2 months ago

It has been proposed to remove the Eq, Hash, and Debug implementations as part of 1.0 https://github.com/rust-lang/libc/issues/3248. The reason for this is that for anything with padding or unions, it is not easy to ensure these are sound. An example is at https://github.com/rust-lang/libc/issues/2816.

I think the problem with this isn't always that they are unsound, but that their usage leads to unsoundness. For example, https://github.com/rust-lang/libc/issues/2700: they definitely shouldn't have relied on sigemptyset to fully initialize the struct, but there isn't anything that looks incorrect at first glance.

At this time, a very rough search shows about 850 Cargo.toml files that make use of this feature, compared to ~50k without it (https://github.com/search?q=lang%3Atoml+%2F%5Elibc%5Cs%2B%3D.*%22extra_traits%22%2F&type=code vs https://github.com/search?q=lang%3Atoml+%2F%5Elibc%5Cs%2B%3D%2F&type=code). This suggests there isn't a whole lot of ecosystem use that will experience breakage, but it also isn't negligible.

It seems Nix may be influenced by this more directly https://github.com/nix-rust/nix/issues/2468.

Personally I think we could leave a Debug implementation but just make non-primitive types nonexhaustive (e.g. sigset_t { .. }). This would at least allow #[derive(Debug)] on anything that wraps these types, and just make it always available rather than behind a feature.

There is a proposed PR up: https://github.com/rust-lang/libc/pull/3692

tgross35 commented 2 months ago

Trying to move discussion here from https://github.com/nix-rust/nix/issues/2468. @asomers said:

Extra unused fields might be unhelpful when implementing certain traits, but how could they make it unsafe? And shouldn't the compiler be able to handle unaligned fields?

For padding fields or reserved fields, reading the data might not be a problem - but we have no clue what the kernel might put there so a derived PartialEq is totally meaningless. Even without padding fields, having the derived implementation makes it easy to assume that the kernel/c library will set all fields to meaningful values which certainly isn't always true.

It is just difficult to justify keeping around with these kind of footguns. Plus having a pointer in the struct makes the equality check meaningless, or at the very least unintuitive. Making sure that everyone only compares the fields they need and know are initialized seems like a pretty reasonable tradeoff.

However, it would be good to see some code examples to better understand how exactly this is currently being used.

Packed structs alone aren't a problem, it's just a restriction on what can be implemented (must be Copy, but most everything has this anyway).