|
| 1 | +- Feature Name: c_void-reunification |
| 2 | +- Start Date: 2018-08-02 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Unify `std::os::raw::c_void` and `libc::c_void` by making them both re-exports |
| 10 | +of a definition in libcore. |
| 11 | + |
| 12 | + |
| 13 | +# Motivation |
| 14 | +[motivation]: #motivation |
| 15 | + |
| 16 | +`std::os::raw::c_void` and `libc::c_void` are different types: |
| 17 | + |
| 18 | +```rust |
| 19 | +extern crate libc; |
| 20 | + |
| 21 | +fn allocate_something() -> *mut std::os::raw::c_void { |
| 22 | + unimplemented!() |
| 23 | +} |
| 24 | + |
| 25 | +fn foo() { |
| 26 | + let something = allocate_something(); |
| 27 | + // ... |
| 28 | + libc::free(something) |
| 29 | +} |
| 30 | +``` |
| 31 | +```rust |
| 32 | +error[E0308]: mismatched types |
| 33 | + --> a.rs:10:16 |
| 34 | + | |
| 35 | +10 | libc::free(something) |
| 36 | + | ^^^^^^^^^ expected enum `libc::c_void`, found enum `std::os::raw::c_void` |
| 37 | + | |
| 38 | + = note: expected type `*mut libc::c_void` |
| 39 | + found type `*mut std::os::raw::c_void` |
| 40 | + |
| 41 | +error: aborting due to previous error |
| 42 | +``` |
| 43 | + |
| 44 | +There is no good reason for this, the program above should compile. |
| 45 | + |
| 46 | +Note that having separate definitions is not as much of a problem for other `c_*` types |
| 47 | +since they are `type` aliases. `c_int` *is* `i32` for example, |
| 48 | +and separate aliases with identical definitions are compatible with each other in the type system. |
| 49 | +`c_void` however is currently defined as an `enum` (of size 1 byte, with semi-private variants), |
| 50 | +and two `enum` types with identical definitions are still different types. |
| 51 | + |
| 52 | +This has been extensively discussed already: |
| 53 | + |
| 54 | +* [Issue #31536: std `c_void` and libc `c_void` are different types](https://github.com/rust-lang/rust/issues/31536) |
| 55 | +* [Internals #3268: Solve `std::os::raw::c_void`](https://internals.rust-lang.org/t/solve-std-os-raw-c-void/3268) |
| 56 | +* [Issue #36193: Move std::os::raw to libcore?](https://github.com/rust-lang/rust/issues/36193) |
| 57 | +* [RFC #1783: Create a separate libc_types crate for basic C types](https://github.com/rust-lang/rfcs/pull/1783) |
| 58 | +* [Issue #47027: Types in std::os::raw should be same as libc crate](https://github.com/rust-lang/rust/issues/47027) |
| 59 | +* [Internals #8086: Duplicate std::os::raw in core?](https://internals.rust-lang.org/t/duplicate-std-raw-in-core/8086) |
| 60 | +* [PR #52839: Move std::os::raw into core](https://github.com/rust-lang/rust/pull/52839) |
| 61 | + |
| 62 | + |
| 63 | +# Guide-level explanation |
| 64 | +[guide-level-explanation]: #guide-level-explanation |
| 65 | + |
| 66 | +With this RFC implemented in both the standard library and in the `libc` crate, |
| 67 | +`std::os::raw::c_void` and `libc::c_void` are now two ways to name the same type. |
| 68 | + |
| 69 | +If two independent libraries both provide FFI bindings to C functions that involve `void*` pointers, |
| 70 | +one might use `std` while the other uses `libc` to access the `c_void` type in order to expose |
| 71 | +`*mut c_void` in their respective public APIs. |
| 72 | +A pointer returned from one library can now be passed to the other library without an `as` pointer cast. |
| 73 | + |
| 74 | +`#![no_std]` crates can now also access that same type at `core::ffi::c_void`. |
| 75 | + |
| 76 | + |
| 77 | +# Reference-level explanation |
| 78 | +[reference-level-explanation]: #reference-level-explanation |
| 79 | + |
| 80 | +In the standard library: |
| 81 | + |
| 82 | +* Create a new `core::ffi` module. |
| 83 | +* Move the `enum` definiton of `c_void` there. |
| 84 | +* In `c_void`’s former location (`std::os::raw`), replace it with a `pub use` reexport. |
| 85 | +* For consistency between `core` and `std`, also add a similar `pub use` reexport at `std::ffi::c_void`. |
| 86 | + (Note that the `std::ffi` module already exists.) |
| 87 | + |
| 88 | +Once the above lands in Nightly, in the `libc` crate: |
| 89 | + |
| 90 | +* Add a build script that detects the existence of `core::ffi::c_void` |
| 91 | + (for example by executing `$RUSTC` with a temporary file like |
| 92 | + `#![crate_type = "lib"] #![no_std] pub use core::ffi::c_void;`) |
| 93 | + and conditionally set a compilation flag for the library. |
| 94 | +* In the library, based on the precence of that flag, |
| 95 | + make `c_void` be either `pub use core::ffi::c_void;` or its current `enum` definition, |
| 96 | + to keep compatibility with older Rust versions. |
| 97 | + |
| 98 | + |
| 99 | +# Drawbacks |
| 100 | +[drawbacks]: #drawbacks |
| 101 | + |
| 102 | +This proposal is a breaking change for users who implement a trait of theirs like this: |
| 103 | + |
| 104 | +```rust |
| 105 | +trait VoidPointerExt {…} |
| 106 | +impl VoidPointerExt for *mut std::os::raw::c_void {…} |
| 107 | +impl VoidPointerExt for *mut libc::c_void {…} |
| 108 | +``` |
| 109 | + |
| 110 | +With the two `c_void` types being unified, the two `impl`s would overlap and fail to compile. |
| 111 | + |
| 112 | +Hopefully such breakage is rare enough that we can manage it. |
| 113 | +Rarity could be evaluated with Crater by either: |
| 114 | + |
| 115 | +* Adding support to Crater if it doesn’t have it already |
| 116 | + for adding a `[patch.crates-io]` section to each root `Cargo.toml` being tested, |
| 117 | + in order to test with a patched `libc` crate in addition to a patched Rust. |
| 118 | + |
| 119 | +* Or speculatively landing the changes in `libc` and publishing them in crates.io |
| 120 | + before landing them in Rust |
| 121 | + |
| 122 | + |
| 123 | +# Rationale and alternatives |
| 124 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 125 | + |
| 126 | +`libc` cannot reexport `std::os::raw::c_void` |
| 127 | +because this would regress compatibility with `#![no_std]`. |
| 128 | + |
| 129 | +[RFC #1783](https://github.com/rust-lang/rfcs/pull/1783) proposed adding |
| 130 | +to the standard library distribution a new crate specifically for the C-compatible types. |
| 131 | +Both `std` and `libc` would depend on this crate. |
| 132 | + |
| 133 | +This was apparently in response to reluctance about having operating-system-dependant definitions |
| 134 | +(such as for `c_long`) in libcore. |
| 135 | +This concern does not apply to `c_void`, whose definition is the same regardless of the target. |
| 136 | +However there was also reluctance to having an entire crate for so little functionality. |
| 137 | + |
| 138 | +That RFC was closed / postponed with this explanation: |
| 139 | + |
| 140 | +> The current consensus is to offer a canonical way of producing |
| 141 | +> an "unknown, opaque type" (a better c_void), possible along the lines of |
| 142 | +> [#1861](https://github.com/rust-lang/rfcs/pull/1861) |
| 143 | +
|
| 144 | +RFC 1861 for `extern` types is now being implemented, but those types are `!Sized`. |
| 145 | +Changing `c_void` from `Sized` to `!Sized` would be a significant breaking change: |
| 146 | +for example, `ptr::null::<c_void>()` and `<*mut c_void>::offset(n)` would not be usable anymore. |
| 147 | + |
| 148 | +We could deprecated `c_void` and replace it with a new differently-named extern type, |
| 149 | +but forcing the ecosystem through that transition seems too costly for this theoretical nicety. |
| 150 | +Plus, this woud still be a nominal type. |
| 151 | +If this new type is to be present if both `libc` and `std`, |
| 152 | + it would still have to be in `core` as well. |
| 153 | + |
| 154 | + |
| 155 | +# Unresolved questions |
| 156 | +[unresolved-questions]: #unresolved-questions |
| 157 | + |
| 158 | +What is the appropriate location for `c_void` in libcore? |
| 159 | + |
| 160 | +This RFC proposes `core::ffi` rather than `core::os::raw` |
| 161 | +on the basis that C-compatible types are misplaced in `std::os::raw`. |
| 162 | +`std::os` is documented as “OS-specific functionality”, |
| 163 | +but everything currently available under `std::os::raw` is about interoperabily with C |
| 164 | +rather than operating system functionality. |
| 165 | +(Although the exact definition of `c_char`, `c_long`, and `c_ulong` does vary |
| 166 | +based on the target operating system.) |
| 167 | +FFI stands for Foreign Function Interface and is about calling or being called from functions |
| 168 | +in other languages such as C. |
| 169 | +So the `ffi` module seems more appropriate than `os` for C types, and it already exists in `std`. |
| 170 | + |
| 171 | +Following this logic to this conclusion, |
| 172 | +perhaps the rest of `std::os::raw` should also move to `std::ffi` as well, |
| 173 | +and the former module be deprecated eventually. |
| 174 | +This is left for a future RFC. |
| 175 | + |
| 176 | +This RFC does not propose any change such as moving to libcore for the C types other than `c_void`. |
| 177 | + |
| 178 | +Although some in previous discussions have expressed desire for using C-compatible types |
| 179 | +without linking to the C runtime libray (which the `libc` crate does) or depending on `std`. |
| 180 | +This use case is also left for a future proposal or RFC. |
0 commit comments