Rust compiler plugins are user-provided libraries that extend the compiler's behavior with new syntax extensions, compile time and lint checks, etc. Questions pertaining to creating and designing Rust compiler plugins go here.
Rust compiler (rustc
) can load various dynamic libraries to extend the existing language syntax - with things like: lints, compile time checking expressions, etc.
Compiler plugins are dynamic libraries that are loaded by calling the following Rust crate attributes:
#![feature(plugin)] // Enable plugin feature game
#![plugin(my_compiler_plugin)] //Instantiate my_compiler_plugin
In the most of cases, a plugin should only be used through #![my_compiler_plugin]
and not through an extern crate item. Linking a plugin would pull in all of libsyntax
and librustc
as dependencies of your crate. This is generally unwanted unless you are building another plugin.
#Procedural macros#
One way compiler plugins can be extended is by using procedural macros. Procedural macros are similar to ordinary macros, except they can change syntax tree during compile time. Using this approach, one can make a macro that is syntactically verified during compilation.
For example we wanted to add a macro that converts roman numerals to decimal, and checks for number correctness during compilation, we'd write something like in roman_numerals.rs:
#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private)]
#![feature(slice_patterns)]
extern crate syntax;
extern crate rustc;
use syntax::codemap::Span;
use syntax::ast::{TokenTree, TtToken};
use syntax::parse::token;
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::build::AstBuilder; // trait for expr_usize
use rustc::plugin::Registry;
fn expand_rn(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
-> Box<MacResult + 'static> {
static NUMERALS: &'static [(&'static str, usize)] = &[
("M", 1000), ("CM", 900), ("D", 500), ("CD", 400),
("C", 100), ("XC", 90), ("L", 50), ("XL", 40),
("X", 10), ("IX", 9), ("V", 5), ("IV", 4),
("I", 1)];
let text = match args {
[TtToken(_, token::Ident(s, _))] => s.to_string(),
_ => {
cx.span_err(sp, "argument should be a single identifier");
return DummyResult::any(sp);
}
};
let mut text = &*text;
let mut total = 0;
while !text.is_empty() {
match NUMERALS.iter().find(|&&(rn, _)| text.starts_with(rn)) {
Some(&(rn, val)) => {
total += val;
text = &text[rn.len()..];
}
None => {
cx.span_err(sp, "invalid Roman numeral");
return DummyResult::any(sp);
}
}
}
MacEager::expr(cx.expr_usize(sp, total))
}
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_macro("rn", expand_rn);
}
Note the #[plugin_registar]
functions that adds plugin as a registered macro. Now, we can use Roman numeral procedural macro (rn!
) by writing:
#![feature(plugin)]
#![plugin(roman_numerals)]
fn main() {
assert_eq!(rn!(MMXV), 2015);
}
#Lints
Another field where syntax extensions are required is providing additional lints for rustc
. For example, let's say you want to define a simple lint that notices functions called lintme
and issues a warning if it encounters them.
The basic structure of it looks something like this (for full example go here):
declare_lint!(TEST_LINT, Warn,
"Warn about items named 'lintme'");
struct Pass;
impl LintPass for Pass {
fn get_lints(&self) -> LintArray {
lint_array!(TEST_LINT)
}
}
impl EarlyLintPass for Pass {
fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {
if it.ident.name == "lintme" {
cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
}
}
}
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_early_lint_pass(box Pass as EarlyLintPassObject);
}
Calling code like
#![plugin(lint_plugin_test)]
fn lintme() { }
produces a compiler warning:
foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default
foo.rs:4 fn lintme() { }
^~~~~~~~~~~~~~~
Important part of lint definition are:
- one or more
declare_lint!
invocations, which define static Lint structs; - a struct holding any state needed by the lint pass (here, there are no such states);
- a LintPass implementation defining how to check each syntax element. A single LintPass may call span_lint for several different Lints, but should register them all through the
get_lints
method.
Instability
All crates using plugins are (due to reliance on rustc
API which is unstable) restricted to only run on rust nightly. This may be offset by using a user made replacement for libsyntax (e.g. syntex )
###More information: