Rust in Meson — 0.57 Edition

Rust in Meson 0.57

Most people by now have heard of the meson build system I hope (at least anyone who would bother to read this blog post), and Rust is the cool new language everyone wants to use because it guards against specific kinds of memory bugs and has some neat features.

The question of course is how to add rust to your existing project using C, C++ or another language. The answer of course, should be build your project with meson.

I’m not going to go into whether using Rust is a good idea, whether it’s going to solve all of your problems, eat your puppy, or any of that. Rust, like everything else we do in programming comes with a set of trade-offs. I’ll say though, for the record, that I like Rust as a programming language form the relatively small amount of it I’ve done.

So here’s a run down of what’s coming in the meson 0.57.0 release for Rust.

Meson level handling for rust editions. Rust editions, for the uninitiated, are roughly equivalent to standards in C and C++. Where you might say gcc -std=c99 or clang++ -std=c++17 Rust has rustc --edition=2018. Meson now handles this using the rust_std=2018 option anywhere you would use c_std=c99 with C.

Meson also turns on a number of options automatically when appropriate, one of them being colorized compiler output. With Meson 0.57, this applies to rustc as well.

I’ve also added support for parsing the rust unit test format to meson test, so you can get subtest output and other good stuff with your rust unit tests.

Along with that we’ve picked up support for linking C ABI binaries into Rust targets (we’ve supported the other way, Rust into C, for a long time), and the ability to use generated sources in Rust targets. One caveat to generated Rust sources is that we still do not have support for mixing generated and static files in the same target, that work will have to come later.

The biggest change though is the addition of an experimental rust module. The goal of this module is to make using Rust in meson not just less painful, but downright pleasant.

With 0.57 it offers two functions, test and bindgen.

Rustmod.test()

To understand test we should talk about Rust unit testing. Like D, and unlike C and C++, unit tests in Rust are usually defined in the same file as the code they test, and a single file is generally compiled twice, once in test mode and once in normal mode. Cargo does this rather painlessly because rustc and cargo are tightly coupled. Meson doesn’t have this idea, because meson has had more work put into C and C++ than other languages.

The test() method helps make Rust unit tests nice and DRY and meson. It’s a rather simple function, it’s signature is: test(target: Executable | Library, dependencies : []Dependency). It copies the source inputs, arguments, dependencies, linking and order requirements, adds the --test argument, and sets the test to use the rust protocol. This means you can just write code like this:

exe = executable(
  'rust_exe',
  'main.rs',
  link_with : my_rust_lib,
  dependencies : dep,
)

rust = import('unstable-rust')
rust.test(exe)

Rustmod.bindgen()

The other help added is around bindgen. Bindgen is a rather useful rust utility that automates writing low level unsafe wrappers around a C API. It does this by loading a C header and generating rust API for that header. This is a bit of a pain to work with in meson because we have an abstraction in include_directories, so you generally don’t pass around lists of -Ifoo style includes.

A wrapper solves that though:

rust = import('unstable-rust')
header_rs = rust.bindgen(
  input : 'myheader.h',
  output : 'myheader.rs',
  include_directories : inc,
)

This is much nicer and a lot less typing. Additionally we can take care of some annoying but useful work for you. Bindgen calls clang under the hood, and clang can generate a make compatible depfile, which ninja can consume. This allows ninja to watch transitive inputs and rebuild as approrpiate without you having to list every header as in input. If myheader.h includes otherheader.h, clang konws this and call tell you, but you’d need to write the appropriate meson configuration to do this. It looks something like

c = custom_target(
  'header.rs',
  input : 'myheader.h',
  output : 'myheader.rs',
  command : [prog_bindgin, '@INPUT@', '--output', '@OUTPUT@', $extra_bindgen_args, '--', '-MD', '-MQ', '@INPUT@', '-MF', '@DEPFILE@', $extra_clang_args, '-Ifoo',],
  depfile : '@PLAINNAME@.d',
  depends : depends,
  depend_files : depend_files,
)

That’s a lot of code for users to copy around, especially the depfile bits. Instead Meson can just handle this for you, and you can worry about writing Rust code.

What’s still to come?

I’m hoping to get two problems sorted out that currently don’t work in 0.58. The first is mixing generated and static sources. The second is Rust’s include!(concat!(env!("OUT_DIR"), "/file.rs")) pattern. This is a little tricky because it very much assumes Cargo’s directory layout, but I think we can get it working (it may just have to look a little different than it would in Cargo), such as include!(env!("MESON_FILE_RS"))

The second thing that I’ve been working on, and am getting close to being ready to show off, is the ability to build Cargo subprojects in meson. I’ll have another blog post talking about that in the future, but for now I’ll say that I think the approach I’m using is workable, and I can nearly build clap (just a few more TODO’s to fix first).

Comments

Popular posts from this blog

This Weekend in Meson++ (February 6th)

Introducing Meson++