Pub(test) visibility modifier

The only place that whitespace is significant in the Rust grammar is in separating tokens. For all other intents and purposes, the parser completely ignores whitespace.


While I agree with @CAD97 that this could be done with a procedural macro, I think it would be better if this was possible without dependencies.

It would be nice if you could just write

pub(cfg!(test)) fn foo() {}

This would require the following:

  • Since cfg! expands to true or false, the grammar would need to be extended to allow pub(true) and pub(false)
  • macro invocations would have to be allowed in this place
1 Like

The ability to apply an arbitrary cfg to just the visibility modifier seems entirely reasonable.

In the interim, you might try pub(crate).


Oh, that is nice because it lets you do feature flag based visibility too.

Do you have some syntax in mind?

pub(test) is a new syntax, and it'll raise questions about when to use it vs #[cfg(test)].

How about making #[cfg(test)] "just work" instead?

I presume the library will have to compiled twice anyway to support pub(test), so it may be simpler to change Rust's policy of compiling non-test library for tests, and just compile everything with cfg=test when testing.


I didn't when I wrote that comment, but I'd imagine something like pub(#[cfg(...)] visibility).

That idea crossed my mind too, but to make a function public only in tests, you'd have to write

pub(#[cfg(not(test))] self) fn ...

Which involves negative reasoning and is more complicated than my idea.

With the syntax I had in mind, the visibility was optional and defaulted to pub, so you could just write pub(#[cfg(test)]).

That also a nice solution. The syntax is a bit weird though, since attributes usually stand before something that is turned on/off.

As a slight variation on @josh's suggestion we could allow an otherwise redundant pub(pub) (alternatively you could think of naked pub be a shorthand for pub(pub)):

pub(#[cfg(test)] pub) fn foo { ... }
pub(#[cfg(test)] crate) fn bar { ... }
pub(#[cfg(test)] in some::path) fn baz { ... }

However, as a consequence this would mean pub() (with empty parentheses) is the equivalent of "not public" - but perhaps you could read it as "public nowhere".


So since (on 2018) pub(in must be followed by crate, self or super, can't pub(in test) or pub(in cfg(test)) (more general) be supported?

Edit: using another keyword to disambiguate would make this possible: pub(in super if cfg(test))

1 Like

The easiest solution would be to have an attribute which is similar to #[cfg_attr], except that it adds a visibility modifier instead of an attribute:

#[cfg_visib(test, pub)]
fn foo() {}

The advantage is that it doesn't need language changes, and anyone who knows #[cfg_attr] will understand how it works.


@Aloso Yeah, that does seem like the easiest solution. (Also, if it's going to be abbreviated, the common abbreviation for visibility is vis, so cfg_vis.)

1 Like

And already in use by the language via the $:vis macro matcher.

1 Like

Also it should be noted that within cfg(test) there is no difference between pub and pub(crate) since you never link against a crate compiled for test. So this is entirely about internal privacy to ensure you don't accidentally use something from a different module other than in unit tests if you were to just make it always pub(crate).

If something like this would be added I would like it to be more general than just for tests, though. E.g., for experiments, or temporary refactoring. E.g., some code that really should be private, but to run experiment A, or to facilitate temporary sister team project B, or ad-hoc customer request C, it needs to be accessible from external location Z.

Is that strictly true? Can't tests in dependent crates depend on public (exported) symbols in yours defined under cfg(test)?

No, not even your own benchmarks/integration tests will see cfg(test) gated code. It only applies to the binary built for cargo test --lib which runs the unit tests.

1 Like

It would be good to find a solution that works for at least integration tests in the same crate. Is there any way to tell the difference between that and a totally independent crate?