| --- | |
| title: Rust | |
| short-description: Working with Rust in Meson | |
| ... | |
| # Using Rust with Meson | |
| ## Avoid using `extern crate` | |
| Meson can't track dependency information for crates linked by rustc as | |
| a result of `extern crate` statements in Rust source code. If your | |
| crate dependencies are properly expressed in Meson, there should be no | |
| need for `extern crate` statements in your Rust code, as long as you use the | |
| Rust 2018 edition or later. This means adding `rust_std=2018` (or later) to the | |
| `project(default_options)` argument. | |
| An example of the problems with `extern crate` is that if you delete a | |
| crate from a Meson build file, other crates that depend on that crate | |
| using `extern crate` might continue linking with the leftover rlib of | |
| the deleted crate rather than failing to build, until the build | |
| directory is cleaned. | |
| This limitation could be resolved in future with rustc improvements, | |
| for example if the [`-Z | |
| binary-dep-depinfo`](https://github.com/rust-lang/rust/issues/63012) | |
| feature is stabilized. | |
| ## Mixing Rust and non-Rust sources | |
| *(Since 1.9.0)* Rust supports mixed targets, but only supports using | |
| `rustc` as the linker for such targets. If you need to use a non-Rust | |
| linker, or support Meson < 1.9.0, see below. | |
| Until Meson 1.9.0, Meson did not support creating a single target with | |
| Rust and non Rust sources mixed together. One had to compile a separate | |
| Rust `static_library` or `shared_library`, and link it into the C build | |
| target (e.g., a library or an executable). | |
| ```meson | |
| rust_lib = static_library( | |
| 'rust_lib', | |
| sources : 'lib.rs', | |
| rust_abi: 'c', | |
| ... | |
| ) | |
| c_lib = static_library( | |
| 'c_lib', | |
| sources : 'lib.c', | |
| link_with : rust_lib, | |
| ) | |
| ``` | |
| ## Mixing Generated and Static sources | |
| *Note* This feature was added in 0.62 | |
| You can use a [[@structured_src]] for this. Structured sources are a dictionary | |
| mapping a string of the directory, to a source or list of sources. | |
| When using a structured source all inputs *must* be listed, as Meson may copy | |
| the sources from the source tree to the build tree. | |
| Structured inputs are generally not needed when not using generated sources. | |
| As an implementation detail, Meson will attempt to determine if it needs to copy | |
| files at configure time and will skip copying if it can. Copying is done at | |
| build time (when necessary), to avoid reconfiguring when sources change. | |
| ```meson | |
| executable( | |
| 'rust_exe', | |
| [[#structured_sources]]( | |
| 'main.rs', | |
| { | |
| 'foo' : ['bar.rs', 'foo/lib.rs', generated_rs], | |
| 'foo/bar' : [...], | |
| 'other' : [...], | |
| } | |
| ) | |
| ) | |
| ``` | |
| ## Use with rust-analyzer | |
| *Since 0.64.0.* | |
| Meson will generate a `rust-project.json` file in the root of the build | |
| directory if there are any rust targets in the project. Most IDEs will need to | |
| be configured to use the file as it's not in the source root (Meson does not | |
| write files into the source directory). [See the upstream | |
| docs](https://rust-analyzer.github.io/book/non_cargo_based_projects.html) for | |
| more information on how to configure that. | |
| ### Clippy | |
| You can use the "clippy-json" build target as rust-analyer's "check command" to recieve clippy diagnostics in your editor. | |
| Without overriding the check command, the LSP will function in a limited state, only showing certain errors (for example, no borrow checking errors are shown). | |
| [Non cargo based projects](https://rust-analyzer.github.io/book/non_cargo_based_projects.html) shows how to override the check command, you probably want to set it to `ninja clippy-json -C build`. | |
| ## Linking with standard libraries | |
| Meson will link the Rust standard libraries (e.g. libstd) statically, unless the | |
| target is a proc macro or dylib, or it depends on a dylib, in which case [`-C | |
| prefer-dynamic`](https://doc.rust-lang.org/rustc/codegen-options/index.html#prefer-dynamic) | |
| will be passed to the Rust compiler, and the standard libraries will be | |
| dynamically linked. | |
| ## Building static libraries for `no_std` environments | |
| Meson by default links all the dependencies of `libstd` into executables that depend | |
| on Rust `staticlib`s. Specifying `b_freestanding=true` lets Meson know that the | |
| `staticlib` uses `#[no_std]`, and reduces the set of libraries that are linked into | |
| its dependencies. | |
| ## Multiple targets for the same crate name | |
| For library targets that have `rust_abi: 'rust'`, the crate name is derived from the | |
| target name. First, dashes, spaces and dots are replaced with underscores. Second, | |
| *since 1.10.0* anything after the first `+` is dropped. This allows creating multiple | |
| targets for the same crate name, for example when the same crate is built multiple | |
| times with different features, or for both the build and the host machine. | |
| ## Compiler vs. linker arguments for Rust | |
| While `rustc` integrates the compiler and linker phase, it is useful | |
| to pass linker arguments to it via the `-Clink-arg=` command line | |
| option. | |
| *Since 1.11.0* `add_project_link_arguments()`, | |
| `add_global_link_arguments()`, the `link_args` keyword argument wrap the | |
| arguments with `-Clink-arg=` before passing them to the Rust compiler. | |
| Furthermore, these arguments are only included when creating binary or | |
| shared library crates. Likewise, methods such as `has_link_argument()` | |
| wrap the arguments being tested with `-Clink-arg=`. | |
| ## Interaction between `rust_panic=abort` and testing | |
| Cargo and Meson build tests differently. `cargo test` recompiles the whole | |
| dependency tree from scratch using the dedicated `test` profile, so the test | |
| build is wholly independent from the artifacts produced by `cargo build`. | |
| Meson instead reuses the libraries that were already built and only compiles an | |
| additional test-harness binary out of the crate's own sources. | |
| Each approach has tradeoffs. Rebuilding everything lets the `test` profile | |
| differ arbitrarily from the `dev` profile, but it is slow and, in the common | |
| case where the two profiles are essentially the same, it duplicates a lot of | |
| work for no benefit. Reusing the libraries is much faster and detects bugs | |
| caused by e.g. compiler optimizations; but it requires the test harness and | |
| the libraries it links against to be ABI compatible. | |
| This has a consequence for the `rust_panic` option, and therefore for the | |
| `panic` key of a Cargo `[profile]`. Rust's test harness relies on catching | |
| panics in order to report failures, so it must be compiled with the default | |
| `unwind` strategy; `rust.test()` and `rust.doctest()` honor this by resetting | |
| `rust_panic` to `none` for the harness even when the rest of the build uses | |
| `rust_panic=abort`. The harness recompiles the crate's own sources with | |
| `unwind`, but `rustc` refuses to link it against a dependency that was compiled | |
| with `-Cpanic=abort`: | |
| ``` | |
| error: the linked panic runtime `panic_unwind` is not compiled with this crate's panic strategy `abort` | |
| ``` | |
| As a result, a crate built with `rust_panic=abort` is effectively | |
| untestable unless it has no dependencies. Therefore, `rust_panic=abort` | |
| is only recommended if you are building a simple `cdylib` or `staticlib` | |
| with most or all of your dependencies in subprojects. In that case, | |
| you can test the dependencies by building the subprojects on their own. | |
| ## Cargo interaction | |
| *Since 1.11.0* | |
| In most cases, a Rust program will use Cargo to download crates. Meson is able | |
| to build Rust library crates based on a `Cargo.toml` file; each external crate | |
| corresponds to a subproject. Rust modules that do not need a `build.rs` file | |
| need no intervention, whereas if a `build.rs` file is present it needs to be | |
| converted manually to Meson code. | |
| To enable automatic configuration of Cargo dependencies, your project must | |
| have `Cargo.toml` and `Cargo.lock` files in the root source directory; | |
| this enables proper feature resolution across crates. You can then | |
| create a workspace object using the Rust module, and retrieve specific | |
| packages from the workspace: | |
| ```meson | |
| rust = import('rust') | |
| cargo_ws = rustmod.workspace() | |
| anyhow_dep = ws.subproject('anyhow').dependency() | |
| ``` | |
| The workspace object also enables configuration of Cargo features, for example | |
| from Meson options: | |
| ```meson | |
| cargo_ws = rustmod.workspace( | |
| features: ['feature1', 'feature2']) | |
| ``` | |
| Finally, the workspace object is able to build targets specified in `lib` | |
| or `bin` sections, extracting compiler arguments for dependencies and | |
| diagnostics from the Cargo.toml file. The simplest case is that of building | |
| a simple binary crate: | |
| ```meson | |
| cargo_ws.package().executable(install: true) | |
| ``` | |
| For a workspace: | |
| ```meson | |
| pkg_lib = cargo_ws.package('myproject-lib') | |
| lib = pkg_lib.library(install: false) | |
| pkg_lib.override_dependency(declare_dependency(link_with: lib)) | |
| cargo_ws.package().executable(install: true) | |
| ``` | |
| Sources are automatically discovered, but can be specified as a | |
| [[@structured_src]] if they are partly generated. | |
| It is still possible to use keyword arguments to link non-Rust build targets, | |
| or even to use the usual Meson functions such as [[static_library]] or | |
| [[executable]]. | |
| ### Non-default and wildcard workspace members | |
| With Cargo, workspace members that are not included in | |
| `workspace.default-members` can be accessed through the `-p` option. | |
| In Meson, non-default members are not part of dependency resolution | |
| by default; the set of optional packages that has to be configured | |
| is specified *since 1.12.0* in the `extra_members` argument of the | |
| `workspace()` method, while up to Meson 1.11.x non-default members would | |
| never be built. | |
| Cargo subprojects will only build default members unless they have a custom | |
| (handwritten) `meson.build` that sets `extra_members` appropriately; | |
| typically the choice of which members to build will come from a Meson | |
| option. | |
| Furthermore, while Cargo accepts glob patterns for the `workspace.members` | |
| field, Meson (*since 1.12.0*) expands such patterns but never treats | |
| globbed patterns as default members. Therefore, they will only be built | |
| if included in `extra_members`. | |
| Note that crates that come from `crates.io` are never workspaces, and | |
| therefore they are not subject to these differences. | |
| ### Subprojects with separate workspaces | |
| *Since 1.13.0* Meson honors the `workspace.exclude` field. Directories | |
| listed there are not workspace members, and therefore they are not | |
| returned by the `packages()` method nor accepted by `package()`. As with | |
| Cargo, excluding a directory also excludes everything below it, entries | |
| are not treated as glob patterns, and a member that is listed literally | |
| in `workspace.members` takes precedence over `workspace.exclude`. | |
| This is useful, for example, when path dependencies come from subprojects | |
| that have their own workspace and `Cargo.lock` files. Because Meson | |
| only supports one `Cargo.lock` file per project, excluded members must | |
| be subprojects. |