IntelliJ Rust: New Functionality for Cargo Features
Like C/C++ and other native languages, Rust includes support for conditional compilation. The most common way to instruct the compiler whether to include or ignore a piece of code in compilation is to add a cfg
attribute with the required condition. For example, you can use this mechanism to check the target architecture and switch code blocks at compile-time depending on the operating system.
IntelliJ Rust detects conditionally disabled blocks in your project and excludes them from the codebase. This means that name resolution and analysis ignore those pieces, so you won’t get errors and warnings inside them, and no items from that code will appear elsewhere in auto-completion. Also, the plugin grays the disabled blocks out in the editor.
This level of cfg
support has been available in the plugin for a long while, but something was missing. Conditional options can include Cargo features. Previously, IntelliJ Rust supported only the features declared in the project’s dependencies, but now your workspace features are supported too.
We’ve implemented a smart UI to make your work with features more transparent: you can enable or disable any specific feature of your workspace right inside a Cargo.toml file. Name resolution and code insight will take that into account.
Let’s examine in detail how IntelliJ Rust handles Cargo features when they are used across various levels of dependency.
Library features
The simplest case is when you need to exclude features from a library, or include only some of them in your build.
To start, let’s add the tokio
crate and enable all of its features using the full
option:
tokio = { version = "0.3.0", features = ["full"] }
If we open tokio
’s Cargo.toml and navigate to the [features]
section, we’ll see the selected checkboxes next to all the features listed in full
:
Now we can specify a particular feature instead of full
, for example, signal
. In tokio
’s Cargo.toml, the checkboxes’ state will change accordingly. Only signal
and the features it depends on are enabled now:
Please note that in the case of external dependencies, this UI is intended to help you grasp the state of features but not to control it, so the checkboxes here in the library’s Cargo.toml are not clickable. If you need to change the list of features, keep using your project’s .toml config.
Independent package features
Another case is when you create your own features inside a package and use them in cfg
blocks. Unlike with external libraries, here you can control the list of features by clearing and selecting the corresponding checkboxes.
Usually, Cargo features are additive and don’t conflict when enabled all together, which is the reason why IntelliJ Rust enables workspace features by default. However, you may want to toggle them on and off, and the plugin’s UI will help you do that!
The ability to toggle workspace features is especially handy when you use them as switchers instead of additive instances (for example, the Amethyst library has features controlling which of the available rendering engines is invoked at the moment). Previously, you would have had to manually remove the unneeded features from your Cargo.toml in order to turn them off.
As the UI now allows you to easily switch between features, be careful not to enable any mutually exclusive ones simultaneously. The plugin will not prevent you from doing so and will not show a warning, because at the .toml file level there’s not enough information for the plugin to detect potential problems in the sources.
Below you can see a multiple resolve situation reported in the sources when non-additive features are enabled at the same time:
Package features that depend on each other
A slightly trickier case is when your features depend on each other:
[features] wrapper = ["calc"] calc = ["core"] core = []
If you toggle one of them on or off, IntelliJ Rust will recognize the dependencies and toggle the related features accordingly.
Features with cross-crate dependencies
Your package features can also depend on features from another crate. Let’s make core
from the previous example dependent on the macros
feature from tokio
:
core = ["tokio/macros"]
Switching into tokio
’s Cargo.toml, we’ll notice how macros
, which is disabled by default, gets enabled when we toggle core
on and gets disabled again when we turn core
off:
Features in a workspace
A more complicated scenario is when features are used in a workspace consisting of several packages.
As a simple example, let’s create a workspace with two packages – bar
and foo
. In foo
’s Cargo.toml, we can declare a dependency on bar
and specify that it uses a feature from bar
called bar_feature
:
When using a workspace, you may want to develop a single crate inside it like an independent instance. In this case, you’ll need control over the crate’s features, with the ability to disable those you don’t need at a certain point.
So what happens if you focus on bar and intentionally disable bar_feature
? As a result, all the code in foo
that uses it will not be resolved, and it might be tricky to find the reason for the errors you’ll get in foo
.
To help you avoid these problems, IntelliJ Rust checks the required features in your workspace dependencies and suggests quick-fixes for them:
Finally, your workspace crates can have features that depend on each other. For example, foo_features
in foo
depending on bar_feature
from bar
:
Here when we disable bar_feature
, the plugin disables foo_feature
automatically. However, when we turn bar_feature
back on, foo_feature
will not get enabled, so you need to roll back the dependent features manually.
In the future we plan to add navigation and completion for features from both .toml and source files, and start supporting cfg_attr
. In the meanwhile, we hope you find these new capabilities helpful, and we really want to hear your feedback! Please share your experience of the new UI and let us know how you would like support for Cargo features in IntelliJ Rust to improve.
Thank you!
Your Rust team
JetBrains
The Drive to Develop