There are some crates which only make sense when compiled for a given operating system, and the authors have expressed no desire to add support for other OSes either (example neli, a netlink crate, and socketcan, a crate to interact with CAN buses). When you accidentally try to compile those crates on an OS they do not target, you get hundreds of errors (symbols not available etc). I wonder how to best avoid that situation and give a nice error message to the user.
Some crates are well served by just using #![cfg(target_os = "...")] inside their lib.rs and then build a compatibility layer on top, but I feel that this just pushes the errors to the crates depending on them and can be just as confusing for the user (at least for the crates mentioned above).
Another solution could be to add a build.rs file that checks the target OS and compile_errors!() if it is unsupported, but that would force everyone to build a trivial build.rs even on builds for the right target platform.
A third idea I had was to add a crate with the sole purpose of failing to compile and providing an explanatory error message why it fails, and conditionally depending on that crate. That would mean you only need to compile this crate if the build is going to fail anyway. On the other hand, your Cargo.lock gains another entry and it might not be immediately obvious why you're depending on such a crate.
Are there other ideas I missed? Is there some community consensus on how to deal with this, or examples for the second and third approach?
I thought so too, at first, but this actually doesn't work in a satisfying way: You get that error somewhere among the hundreds of other errors, unfortunately. It seems you really need to either conditionally compile the crate or only the error message, or prevent the crate from starting to compile, hence the build script
You can put your implementation using platform-specific code in a module that you #[cfg] out. Essentially the idea is to get only one error if the platform is wrong:
#[cfg(not(target_os = "linux"))]
compile_error!("This crate only supports Linux platforms");
#[cfg(target_os = "linux")]
mod imp {
// some Linux specific code, maybe in imp.rs/linux.rs file
}
use imp::ImplementationOfThing;
pub struct PublicApiStruct {
inner: ImplementationOfThing,
}
If you aren’t on Linux, this gives a decent error message:
Compiling playground v0.0.1 (/playground)
error: This crate only supports Linux platforms
--> src/lib.rs:2:1
|
2 | compile_error!("This crate only supports Linux platforms");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0432]: unresolved import `imp`
--> src/lib.rs:10:5
|
10 | use imp::ImplementationOfThing;
| ^^^ help: a similar path exists: `simd_adler32::imp`
For more information about this error, try `rustc --explain E0432`.
error: could not compile `playground` (lib) due to 2 previous errors
You can also use #![cfg(target_os)], and once your crate’s user starts trying to use functions they won’t be found. Just document it very clearly at the top of the README that your crate is platform-specific and it should be obvious.
Yes, I mentioned the #![cfg()] options in the OP already, the problem is that for transitive dependencies they just shift the errors down some crates. But I like @cod10129's idea with the modules and will see if that would work