Skip to content

Commit d742eed

Browse files
committed
Auto merge of #1536 - Aaron1011:feature/const-fn, r=gnzlbg
Add support for making functions `const` PR rust-lang/rust#64906 adds the ability to write `const extern fn` and `const unsafe extern fn`, which will allow manys functions in `libc` to become `const`. This is particuarly useful for functions which correspond to C macros (e.g. `CMSG_SPACE`). In C, these macros are constant expressions, allowing them to be used when declaring arrays. However, since the corresponding `libc` functions are not `const`, writing equivalent Rust code is impossible. Users must either perform an unecessary heap allocation, or pull in `bindgen` to evaluate the macro for specific values (e.g. `CMSG_SPACE(1)`). However, the syntax `const extern fn` is not currently parsed by rust. To allow libc to use this without breaking backwards compatibility (i.e. bumping the minimum Rust version), I've taken the following approach: 1. A new off-by-default feature `extern-const-fn` is added to `libc`. 2. The internal `f!` macro has two versions, selected at compile-time by a `cfg_if`. When `extern-const-fn` is enabled, the declared `f!` macro passes through the `const` keyword from the macro user to the final definition (`pub const unsafe extern fn foo`. When `extern-const-fn` is disabled, the `const` keyword passed by the macro user is discarded, resulting in a plain `pub extern const fn` being declared. Unfortunately, I couldn't manage to get `macro_rules` to accept a normal `const` token in the proper place (after `pub`). I had to resort to placing it in curly brackets: ```rust pub {const} fn foo(val: u8) -> i8 { } ``` The `f!` macro then translates this to a function definition with `const` in the proper position. I'd appreciate it if someone who's more familiar with `macro_rules!` could see if I missed a way to get the desired syntax.
2 parents 5e5c1ee + ca2d53e commit d742eed

File tree

8 files changed

+130
-16
lines changed

8 files changed

+130
-16
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ std = []
2828
align = []
2929
rustc-dep-of-std = ['align', 'rustc-std-workspace-core']
3030
extra_traits = []
31+
const-extern-fn = []
3132
# use_std is deprecated, use `std` instead
3233
use_std = [ 'std' ]
3334

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ libc = "0.2"
3535
* `extra_traits`: all `struct`s implemented in `libc` are `Copy` and `Clone`.
3636
This feature derives `Debug`, `Eq`, `Hash`, and `PartialEq`.
3737

38+
* `const-extern-fn`: Changes some `extern fn`s into `const extern fn`s.
39+
This features requires a nightly rustc
40+
3841
* **deprecated**: `use_std` is deprecated, and is equivalent to `std`.
3942

4043
## Rust version support

build.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ use std::process::Command;
33
use std::str;
44

55
fn main() {
6-
let rustc_minor_ver =
7-
rustc_minor_version().expect("Failed to get rustc version");
6+
let (rustc_minor_ver, is_nightly) =
7+
rustc_minor_nightly().expect("Failed to get rustc version");
88
let rustc_dep_of_std = env::var("CARGO_FEATURE_RUSTC_DEP_OF_STD").is_ok();
99
let align_cargo_feature = env::var("CARGO_FEATURE_ALIGN").is_ok();
10+
let const_extern_fn_cargo_feature =
11+
env::var("CARGO_FEATURE_CONST_EXTERN_FN").is_ok();
1012
let libc_ci = env::var("LIBC_CI").is_ok();
1113

1214
if env::var("CARGO_FEATURE_USE_STD").is_ok() {
@@ -72,9 +74,16 @@ fn main() {
7274
if rustc_dep_of_std {
7375
println!("cargo:rustc-cfg=libc_thread_local");
7476
}
77+
78+
if const_extern_fn_cargo_feature {
79+
if !is_nightly || rustc_minor_ver < 40 {
80+
panic!("const-extern-fn requires a nightly compiler >= 1.40")
81+
}
82+
println!("cargo:rustc-cfg=libc_const_extern_fn");
83+
}
7584
}
7685

77-
fn rustc_minor_version() -> Option<u32> {
86+
fn rustc_minor_nightly() -> Option<(u32, bool)> {
7887
macro_rules! otry {
7988
($e:expr) => {
8089
match $e {
@@ -93,7 +102,13 @@ fn rustc_minor_version() -> Option<u32> {
93102
return None;
94103
}
95104

96-
otry!(pieces.next()).parse().ok()
105+
let minor = pieces.next();
106+
let nightly_raw = otry!(otry!(pieces.next()).split('-').nth(1));
107+
let nightly =
108+
nightly_raw.starts_with("dev") || nightly_raw.starts_with("nightly");
109+
let minor = otry!(otry!(minor).parse().ok());
110+
111+
Some((minor, nightly))
97112
}
98113

99114
fn which_freebsd() -> Option<i32> {

ci/build.sh

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ test_target() {
6767
cargo "+${RUST}" "${BUILD_CMD}" -vv $opt --no-default-features --target "${TARGET}" \
6868
--features extra_traits
6969

70+
# Test the 'const-extern-fn' feature on nightly
71+
if [ "${RUST}" = "nightly" ]; then
72+
cargo "+${RUST}" "${BUILD_CMD}" -vv $opt --no-default-features --target "${TARGET}" \
73+
--features const-extern-fn
74+
fi
75+
76+
7077
# Also test that it builds with `extra_traits` and default features:
7178
if [ "$NO_STD" != "1" ]; then
7279
cargo "+${RUST}" "${BUILD_CMD}" -vv $opt --target "${TARGET}" \

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#![no_std]
3535
#![cfg_attr(feature = "rustc-dep-of-std", no_core)]
3636
#![cfg_attr(target_os = "redox", feature(static_nobundle))]
37+
#![cfg_attr(libc_const_extern_fn, feature(const_extern_fn))]
3738

3839
#[macro_use]
3940
mod macros;

src/macros.rs

+89-9
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,96 @@ macro_rules! s_no_extra_traits {
121121
);
122122
}
123123

124-
#[allow(unused_macros)]
125-
macro_rules! f {
126-
($(pub fn $i:ident($($arg:ident: $argty:ty),*) -> $ret:ty {
127-
$($body:stmt);*
128-
})*) => ($(
129-
#[inline]
130-
pub unsafe extern fn $i($($arg: $argty),*) -> $ret {
131-
$($body);*
124+
// This is a pretty horrible hack to allow us to conditionally mark
125+
// some functions as 'const', without requiring users of this macro
126+
// to care about the "const-extern-fn" feature.
127+
//
128+
// When 'const-extern-fn' is enabled, we emit the captured 'const' keyword
129+
// in the expanded function.
130+
//
131+
// When 'const-extern-fn' is disabled, we always emit a plain 'pub unsafe extern fn'.
132+
// Note that the expression matched by the macro is exactly the same - this allows
133+
// users of this macro to work whether or not 'const-extern-fn' is enabled
134+
//
135+
// Unfortunately, we need to duplicate most of this macro between the 'cfg_if' blocks.
136+
// This is because 'const unsafe extern fn' won't even parse on older compilers,
137+
// so we need to avoid emitting it at all of 'const-extern-fn'.
138+
//
139+
// Specifically, moving the 'cfg_if' into the macro body will *not* work.
140+
// Doing so would cause the '#[cfg(feature = "const-extern-fn")]' to be emiited
141+
// into user code. The 'cfg' gate will not stop Rust from trying to parse the
142+
// 'pub const unsafe extern fn', so users would get a compiler error even when
143+
// the 'const-extern-fn' feature is disabled
144+
//
145+
// Note that users of this macro need to place 'const' in a weird position
146+
// (after the closing ')' for the arguments, but before the return type).
147+
// This was the only way I could satisfy the following two requirements:
148+
// 1. Avoid ambuguity errors from 'macro_rules!' (which happen when writing '$foo:ident fn'
149+
// 2. Allow users of this macro to mix 'pub fn foo' and 'pub const fn bar' within the same
150+
// 'f!' block
151+
cfg_if! {
152+
if #[cfg(libc_const_extern_fn)] {
153+
#[allow(unused_macros)]
154+
macro_rules! f {
155+
($(pub $({$constness:ident})* fn $i:ident(
156+
$($arg:ident: $argty:ty),*
157+
) -> $ret:ty {
158+
$($body:stmt);*
159+
})*) => ($(
160+
#[inline]
161+
pub $($constness)* unsafe extern fn $i($($arg: $argty),*
162+
) -> $ret {
163+
$($body);*
164+
}
165+
)*)
132166
}
133-
)*)
167+
168+
#[allow(unused_macros)]
169+
macro_rules! const_fn {
170+
($($({$constness:ident})* fn $i:ident(
171+
$($arg:ident: $argty:ty),*
172+
) -> $ret:ty {
173+
$($body:stmt);*
174+
})*) => ($(
175+
#[inline]
176+
$($constness)* fn $i($($arg: $argty),*
177+
) -> $ret {
178+
$($body);*
179+
}
180+
)*)
181+
}
182+
183+
} else {
184+
#[allow(unused_macros)]
185+
macro_rules! f {
186+
($(pub $({$constness:ident})* fn $i:ident(
187+
$($arg:ident: $argty:ty),*
188+
) -> $ret:ty {
189+
$($body:stmt);*
190+
})*) => ($(
191+
#[inline]
192+
pub unsafe extern fn $i($($arg: $argty),*
193+
) -> $ret {
194+
$($body);*
195+
}
196+
)*)
197+
}
198+
199+
#[allow(unused_macros)]
200+
macro_rules! const_fn {
201+
($($({$constness:ident})* fn $i:ident(
202+
$($arg:ident: $argty:ty),*
203+
) -> $ret:ty {
204+
$($body:stmt);*
205+
})*) => ($(
206+
#[inline]
207+
fn $i($($arg: $argty),*
208+
) -> $ret {
209+
$($body);*
210+
}
211+
)*)
212+
}
213+
}
134214
}
135215

136216
#[allow(unused_macros)]

src/unix/linux_like/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1165,8 +1165,10 @@ pub const ARPHRD_IEEE802154: u16 = 804;
11651165
pub const ARPHRD_VOID: u16 = 0xFFFF;
11661166
pub const ARPHRD_NONE: u16 = 0xFFFE;
11671167

1168-
fn CMSG_ALIGN(len: usize) -> usize {
1169-
len + ::mem::size_of::<usize>() - 1 & !(::mem::size_of::<usize>() - 1)
1168+
const_fn! {
1169+
{const} fn CMSG_ALIGN(len: usize) -> usize {
1170+
len + ::mem::size_of::<usize>() - 1 & !(::mem::size_of::<usize>() - 1)
1171+
}
11701172
}
11711173

11721174
f! {
@@ -1182,7 +1184,7 @@ f! {
11821184
cmsg.offset(1) as *mut ::c_uchar
11831185
}
11841186

1185-
pub fn CMSG_SPACE(length: ::c_uint) -> ::c_uint {
1187+
pub {const} fn CMSG_SPACE(length: ::c_uint) -> ::c_uint {
11861188
(CMSG_ALIGN(length as usize) + CMSG_ALIGN(::mem::size_of::<cmsghdr>()))
11871189
as ::c_uint
11881190
}

tests/const_fn.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![cfg(libc_const_extern_fn)] // If this does not hold, the file is empty
2+
3+
#[cfg(target_os = "linux")]
4+
const _FOO: libc::c_uint = unsafe { libc::CMSG_SPACE(1) };
5+
//^ if CMSG_SPACE is not const, this will fail to compile

0 commit comments

Comments
 (0)