Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
866 views
in Technique[技术] by (71.8m points)

rust - How do I change a function's qualifiers via conditional compilation?

I have a function that is capable of being implemented as a const:

#![feature(const_fn)]

// My crate would have:

const fn very_complicated_logic(a: u8, b: u8) -> u8 {
    a * b
}

// The caller would have:

const ANSWER: u8 = very_complicated_logic(1, 2);

fn main() {}

I'd like to continue to support stable Rust where it's not possible to define such functions. These stable consumers would not be able to use the function in a const or static, but should be able to use the function in other contexts:

// My crate would have:

fn very_complicated_logic(a: u8, b: u8) -> u8 {
    a * b
}

// The caller would have:    

fn main() {
    let answer: u8 = very_complicated_logic(1, 2);
}

How can I conditionally compile my code so that adventurous users of my crate could enable const fn support, stable users would still be able to use my code, and I don't have to write every function twice?

The same question should apply to the other modifiers of a function, but I'm not sure of concrete cases where these modifiers would change based on some condition:

  • default
  • unsafe
  • extern
  • pub (and other visibility modifiers)
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Macros to the rescue!

#![cfg_attr(feature = "const-fn", feature(const_fn))]

#[cfg(not(feature = "const-fn"))]
macro_rules! maybe_const_fn {
    ($($tokens:tt)*) => {
        $($tokens)*
    };
}

#[cfg(feature = "const-fn")]
macro_rules! maybe_const_fn {
    ($(#[$($meta:meta)*])* $vis:vis $ident:ident $($tokens:tt)*) => {
        $(#[$($meta)*])* $vis const $ident $($tokens)*
    };
}

maybe_const_fn! {
    #[allow(unused)] // for demonstration purposes
    pub fn very_complicated_logic(a: u8, b: u8) -> u8 {
        internally_complicated_logic(a, b)
    }
}

maybe_const_fn! {
    fn internally_complicated_logic(a: u8, b: u8) -> u8 {
        a * b
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(feature = "const-fn")]
    #[test]
    fn use_in_const() {
        const ANSWER: u8 = very_complicated_logic(1, 2);
        drop(ANSWER);
    }

    #[test]
    fn use_in_variable() {
        let answer: u8 = very_complicated_logic(1, 2);
        drop(answer);
    }
}

Along with this in Cargo.toml:

[features]
const-fn = []

Since macros can only expand to complete pieces of syntax (i.e. a macro cannot simply expand to const), we have to wrap the whole function in the macro and leave some parts of it unparsed so that we can inject const in the appropriate place. Then, the parser can parse the whole thing as a function definition.

Attributes and visibility qualifiers need special treatment, because they must appear before const. I am using the vis matcher (available since Rust 1.30.0) to simplify the macro's implementation.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...