BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Rust 1.30 Brings More Metaprogramming Support and Improved Modules

Rust 1.30 Brings More Metaprogramming Support and Improved Modules

This item in japanese

The latest release of Rust, version 1.30 extends procedural macros by allowing them to define new attributes and function-like macros. Additionally, it streamlines the Rust module system by making it more consistent and straightforward.

Rust 1.30 introduces two new types of procedural macros, "attribute-like procedural macros" and "function-like procedural macros". Procedural macros are Rust metaprogramming foundation and enable the manipulation of a program syntax tree. In this respect procedural macros are much more powerful than declarative macros, which provide a mechanism to define a shorthand for more complex code based on pattern matching.

Attribute-like procedural macros are similar to existing derive macros but are more flexible in that they allow you to create new attributes and may be applied also to functions in addition to structs and enums. For example, an attribute macro could enable the specification of a route attribute to define HTTP routing:

// use of route procedural macro
#[route(GET, "/")]
fn index() {
 ...
}

// procedural macro defining route
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
  // attr receives the GET, "/" part of the macro
  // item receives fn index () { ...
}

Similarly, function-like procedural macros allows you to define macros that look like functions, e.g.:

// parse an SQL statement
let sql = sql!(SELECT * FROM posts WHERE id=1);

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

In both examples, TokenStream represents the syntax tree the attribute is applied to or the attribute/function definition. The route/sql function converts the received syntax tree into a new syntax tree which is returned to the caller, i.e., generating new code to execute.

Rust 1.30 also brings a few changes to the use macro to improve developer experience when using Rust module system. Firstly, use can now bring in a macro definition, thus making the macro_use annotation obsolete:

// old:
#[macro_use]
extern crate serde_json;

// new:
extern crate serde_json;
use serde_json::json;

Additionally, external crates are now more resilient to functions being moved across the module hierarchy by ensuring all references to a namespace are checked against all extern crate directives included in the module prelude and using the one that matches, if any. Previously, you had to explicitly use extern inside of a module or use the ::extern_name syntax, as shown in the following example:

extern crate serde_json;

fn main() {
    let json = serde_json::from_str("..."); // OK
}

mod foo {

    // to use serde_json in this module you explicitly use it
    use serde_json;

    fn bar() {
        let json = serde_json::from_str("...");
    }

    fn baz() {
      // alternatively, you fully qualify the external module name
      let json = ::serde_json::from_str("...");
    }

Finally, use is now more consistent in the way it interprets module paths. You can use now the crate keyword to indicate that you would like the module path to start at your crate root. Previous to 1.30, this was the default for module paths but paths referring to items directly would start at the local path:

mod foo {
    pub fn bar() {
        // ...
    }
}

mod baz {
    pub fn qux() {
    
        // old
        ::foo::bar();
        // does not work, which is different than with `use`:
        // foo::bar();

        // new
        crate::foo::bar();
    }
}

More changes brought by Rust 1.30 are the following:

  • You can now use keywords as identifiers by prefixing them with r#, e.g. r#for. This change is mostly motivated by the fact that Rust 2018 will introduce new keywords, so a mechanism shall be available to convert existing code using those keywords as variable or function names.
  • It is now possible to build applications not using the standard library with no_std. Previously, you could only build libraries with no_std due to the impossibility of defining a panic handler.

You can update your Rust distribution using $ rustup update stable. For full detail of Rust 1.30 do not miss the release notes.

Rate this Article

Adoption
Style

BT