Best practice to write a macro for both std and no_std?


#1

What’s the best practice to write a macro to work in both no_std and std packages?

More details: I have a macro that uses some types and functions from fmt, char and hash modules, limited to the overlaps of std and core. To be able to name these items, I have to have the full path inside the macro code. Now, wether core is available as the top-level lang library, or std, that depends on the caller of the macro, but not the macro’s package itself.

What I have started to do right now is to expect all caller package to explicitly have extern crate core; in the code, if they are not no_std. But this solution doesn’t feel right.

Unfortunately, no_std is not testable at compile-time using cfg(). Macros are a good reason to actually make no_std a config, but I couldn’t find any previous discussion from this perspective.

So, what do you think? Are there any better ways to handle this? Should no_std be a testable config in compile time?


#2

Should no_std be a testable config in compile time?

I believe the common practice is to have your own std feature, enabled by default. This you can easily test for.

For point of reference, here’s what itertools does:

#[cfg(not(feature = "use_std"))]
extern crate core as std;

// ...
// for compatibility with no std and macros
#[doc(hidden)]
pub use std::iter as __std_iter;

// ...
macro_rules! izip {
    // ...
    ( $first:expr $( , $rest:expr )* $(,)* ) => {
        $crate::__std_iter::IntoIterator::into_iter($first)
            // ...
    };
}

#3

Thanks, @ExpHP. Right, with $crate it’s possible to move the dependency in-package. I’m now doing so.

About the feature, I’ve seen it named just std in many places, and recommended over use_std. So, I’m going with std.

Just one question: why did you say “enabled by default”? I’m actually going with no-std-by-default where possible, and have std feature only if there’s extra API available that way.

Since almost all libcore is just reexported in libstd, there shouldn’t be any harm to use core::* variants all the time, even when library is used in a use-std package. Is that the right assumption?


#4

I thought this was the case (which is why I said "std feature") but had trouble actually finding an example. :stuck_out_tongue:

Depends on the target userbase I guess. For applications where no_std doesn’t have a very strong presence, functions with std types might look pretty innocuous to most people and it could be surprising if a feature is necessary to access them. (personally, no_std isn’t something I dedicate much thought to myself)

Erp, sorry, I spoke too soon. Re-reading the discussion I see that everything you’re using is in the overlap of core and std, so in that case I guess limiting it to core is no issue.


#5

You shouldn’t need a std feature if you only intend to use things from core. I usually define an export module to re-export the types my macro depends on.

extern crate core;

// Not public API.
#[doc(hidden)]
pub mod export {
    pub use core::marker::PhantomData;
}

#[macro_export]
macro_rules! behnam {
    ($name:ident) => {
        pub struct $name<T>($crate::export::PhantomData<T>);
    }
}

#6

It seems like we need a pub(macro) sort of thing for this, if it’s a common pattern.


#7

Macros 2.0 will fix this in an even better way than pub(macro). As I understand it, the macro body will have access to everything that is in scope in the location the macro is defined (not in the location the macro is invoked), plus anything from the scope in which the macro is invoked that is explicitly passed to the macro. So basically it works just like how we are all used to scopes working when calling a function from another crate.


#8

This sounds great! Now we will finally be able to catch up technologically with emacs 24 lisp!

:turkey: