Optional features are part of the crate — the project should say so

When a crate defines an optional feature like mock, and there's a module behind #[cfg(feature = "mock")], that code is part of the crate. It compiles. It’s tested. It’s documented. It’s not an afterthought — it’s intentional.

But when someone opens the project, that code is often grayed out. The file shows up as “not included in any crates.” Go-to-def doesn’t work. It’s as if it’s not there.

This isn’t because the code is optional. It’s because the project hasn’t said: “This feature is meant to be used.”

We already have a place for project-local intent: .cargo/config.toml. It’s where we set RUSTFLAGS, define targets, and configure environment variables. It’s version-controlled, shared, and respected by Cargo, build scripts, and tools.

And rust-analyzer already reads from it — whenever it sees [env].

So it’s natural for it to also express: “When analyzing this crate, these features are active.”

# .cargo/config.toml
[unstable]
rust-analyzer.cargo.features = "all"

or

[unstable]
rust-analyzer.cargo.features = ["mock"]

This doesn’t change how cargo build works. It doesn’t affect compilation. It just aligns analysis with the project’s intent — the same way [env] already does.

No setup. No terminal tricks. No editor-specific files.

If a feature exists and is meant to be used, the project should be able to say so — once, clearly, in the same place we already use for project configuration.

We already have the mechanism. This is just using it.

Was this post written by a LLM?

13 Likes

No

1 Like

rust-analyzer already has a configuration setting for controlling features, see Configuration - rust-analyzer

4 Likes

Yes, but rust-analyzer is not looking into cargo config for it's own config key.

What benefit are you expecting by rust-analyzer reading .cargo/config.toml?

1 Like

The configuration is more directed to the editor. So we must use config files for each editor. I wish to version control that analysis can be done even over certain optional features. I don't the editor is using, I actually don't want to care. I do know that the rust-analyser is more important, and I don't like to setup over and over again for every editor. Rust-analyser does have the feature. They also already use the cargo config. I think it is a project specific setting and not a editor one. Keeps my git clean. :slight_smile: And I stumbled upon it because, setting it in the zed editor did not work -> so me thinking what could be better.

Sometimes I need the view with a feature disabled, to ensure that the code is correct without it, especially in situations where the cfgs don't purely add code but switch from one implementation to another.

Therefore, I think that rust-analyzer (and other language tooling) should not treat the set of enabled features as configuration, but as current editing state. It should be easy to toggle without changing any files that might be in version control.

From this perspective, the thing that should go in config files is not “enable these features” but something like “recommended ‘complete’ set of features”. I don’t know that this changes the proposed design at all, since if this Cargo config key existed it would presumably get overridden by .vscode/settings.json if present, but I thought it is worth mentioning.

9 Likes

You can add something like in your Cargo.toml

[dev-dependencies]
mycrate = { path = ".", features = ["stuff", "disabled", "by", "default"] }

and rust-analyzer picks those features. You'll probably need them enabled anyway if you want to add any tests.

Also configuration for rust-analyzer can go not only into editor specific files but also into rust-analyzer.toml sitting in a project root - doc mentions that the support is somewhat spotty though. I imagine they would take a pull request adding some support that is missing.

It is up to the project maintainer to do a suggestion. It indeed feels very important for a user to override it with the editors config. It is perfect for that.

Not finding the issue right now but this can get confusing results in some cases.

This also makes it more difficult to test without features which can be important in some cases.

That is touching testing, but I was thinking about just developing. Optional features like mock, tracing or serde would not be handled by rust-analyzer. That is not always the reflection of the crate's intent.

For the second part. The rust-analyzer.toml file seems to me like a more user-specific config you can add to .gitignore. Some rust-analyzer config looks more like project- or workspace-specific suggestions from a project maintainer. When rust-analyzer also reads them from .cargo/config.toml, it can be part of that shared crate.

A more flexible way to get this same effect today is to add another package in the same workspace which has this dependency. Then, builds using the default set of workspace members will have the features, and cargo test -p mycrate will not.

But it might not be desirable to apply the features to all default-members builds, only editing. So, it might make sense for Cargo to offer a feature list, not as a config, but as a workspace property or properties — some sort of workspace.default-features-for-development. This would have the advantage over config that the features apply exactly to the workspace (which is the scope at which feature names mean something) rather than the current directory.

A hint from cargo.toml for rust-analyzer can be used to include optional features. But for that to make sense it should also benefit Cargo, and I don't know enough about Cargo's design to say if that's justified.

Rust-analyzer already has the ability to read project settings. It respects [env] from .cargo/config.toml. Though I believe it does so through Cargo, not by parsing the file directly (Somebody might confirm this). Since work on rust-analyzer support is experimental (could not make it work yet), it would make it easier to also read their own keys from .cargo/config.toml. The experiment does give me the idea that there is a demand for project-level config. I think my proposal is also an addition to the rust-analyzer config file.

In general, I think of it this way:

  • Project intent -> shared defaults .cargo/config.toml
  • User preference -> local settings rust-analyzer.toml
  • Editor choice -> editor-specific settings (e.g. .vscode/, .zed/)

All three can be version controlled separately if preferred.

Project-level config could go in rust-analyzer.toml, but then there's no clean distinction between a crate's suggestion and a user preference. As rust-analyzer already uses [env] from .cargo/config.toml, it makes sense to also use it to set defaults. Using a cargo config also works in workspaces.

I think that default-features-for-development would benefit Cargo workflows, too. In particular, if cargo test enabled those features by default, then cargo test without arguments would be closer to “test everything reasonable”, instead of the status quo of not testing anything that’s an optional feature not enabled by anything in the workspace.

I do not like that behavior because I have CI do cargo test --frozen --locked to test exactly what is built. I would be quite unhappy if I would have to "fight" some defaults that differ from build behaviors.

Shouldn't CI test also at least with no features and with all festures?

1 Like

I prefer to use cargo hack test --feature-powerset (with --include-features and --exclude-features as needed, e.g. for platform-specific or platform-incompatible features).

I also overrode rust-analyzer.check.overrideCommand in .vscode/settings.json to a program that makes (and caches) several calls to cargo hack clippy --feature-powerset --depth 1. The result is that all errors, from any tested cfg combination, are overlaid. However, it's not enough for go-to-def or type hints... cfg-d out code still gets grayed out as normal.

I think the ideal would be for rust-analyzer to support overlaying results from multiple sets of cfgs, targets, and so on without having to use third-party workarounds. (I fear the cost in RAM usage might be high, but then again, my check.overrideCommand does a pretty bad job of caching stuff and still works fine.)

Yes, I have multiple sets of jobs for such cases. Each job set uses a CARGO_FEATURES to set the flags to use for each, so I expect build and test to behave similarly for the same set of flags.