Crates.io | stageleft |
lib.rs | stageleft |
version | 0.5.0 |
source | src |
created_at | 2024-01-29 19:19:06.706442 |
updated_at | 2024-11-08 19:28:13.756222 |
description | Type-safe staged programming for Rust |
homepage | |
repository | |
max_upload_size | |
id | 1119247 |
size | 40,642 |
Stageleft makes it easy to write type-safe code generators. For example, consider a function that raises a number to a power, but the power is known at compile time. Then, we can compile away the power into repeatedly squaring the base. We can implement a staged program for this:
use stageleft::{q, BorrowBounds, IntoQuotedOnce, Quoted, RuntimeData};
#[stageleft::entry]
fn raise_to_power(_ctx: BorrowBounds<'_>, value: RuntimeData<i32>, power: u32) -> impl Quoted<i32> {
if power == 1 {
q!(value).boxed()
} else if power % 2 == 0 {
let half_result = raise_to_power(_ctx, value, power / 2);
q!({
let v = half_result;
v * v
})
.boxed()
} else {
let half_result = raise_to_power(_ctx, value, power / 2);
q!({
let v = half_result;
(v * v) * value
})
.boxed()
}
}
The q!(...)
macro quotes code, which means that it will be spliced into the final generated code. We can take in the unknown base as a runtime parameter (RuntimeData<i32>
), but the power is known at compile time so we take it as a u32
. The _ctx
parameter is unused in this case, because we are returning any borrowed data (see stageleft::entry
for more details). The .boxed()
API allows us to return different pieces of spliced code from the same function, and the impl Quoted<i32>
return type tells the compiler that the function will return a piece of code that evaluates to an i32
. We can invoke this staged function just like a regular Rust macro:
let result = raise_to_power!(2, 5);
assert_eq!(result, 1024);
But if we expand the macro, we can see that the code has been optimized (simplified for brevity):
{
fn expand_staged(value: i32) -> i32 {
let v = {
let v = {
let v = value;
v * v // 2^2
};
(v * v) * value // 2^5
};
v * v // 2^10
}
expand_staged(2)
}
Stageleft requires a particular workspace setup, as any crate that uses Stageleft must have an supporting macro crate (whose contents will be automatically generated). For a crate named foo
, you will also need a helper crate foo_macro
.
The main crate foo
will need the following Cargo.toml
:
[package]
// ...
[dependencies]
stageleft = "0.1.0"
foo_macro = { path = "../foo_macro" }
[build-dependencies]
stageleft_tool = "0.1.0"
The helper crate should have the following Cargo.toml
:
[package]
name = "foo_macro"
// ...
[lib]
proc-macro = true
path = "../foo/src/lib.rs"
[features]
default = ["macro"]
macro = []
[dependencies]
// all dependencies of foo
[build-dependencies]
stageleft_tool = "0.1.0"
Next, you will need to set up build.rs
scripts for both of your crates.
In foo
:
fn main() {
stageleft_tool::gen_final!();
}
and in foo_macro
:
use std::path::Path;
fn main() {
stageleft_tool::gen_macro(Path::new("../foo"), "foo");
}