1个不稳定版本
使用旧的Rust 2015
0.1.0 | 2016年6月8日 |
---|
#553 在 编程语言
在 lia-plugin 中使用
1MB
20K SLoC
Lia:Rust的高级语言
俩 (liǎ) - 1.两个,2.一对
Lia是一种动态类型和垃圾回收的编程语言,通过使用Rust作为编译目标,与Rust无缝交互。这使得Lia用户在需要时可以降低到高效的Rust代码,但对于大多数应用程序,可以使用高级的JavaScript式语言。例如,绑定到矩阵库(类似于numpy)非常简单
// lia! declares a set of Lia functions. It is a procedural macro that compiles Lia into Rust.
lia! {
function multiply_matrices() {
console.log("Multiplying matrices");
var x = @Matrix::from_list([[4, 3], [2, 1]]); // The @ means a foreign (Rust) function
var y = @Matrix::from_list([[1, 2], [3, 4]]);
var z = @Matrix::multiply(x, y);
return @Matrix::get(z, 0, 0);
}
}
#[derive(Clone)]
struct Matrix {
data: Vec<i32>,
rows: i32,
cols: i32,
}
// Putting #[lia_impl_glue] on an impl block will automatically generate functions that
// do the appropriate type-casting from Lia's dynamic types into Rust's static types.
#[lia_impl_glue]
impl Matrix {
// some functions omitted...
pub fn multiply(&self, other: &Matrix) -> Matrix {
assert!(self.cols == other.rows);
let mut new_mat = Matrix::new(self.rows, other.cols);
for i in 0..self.rows {
for j in 0..other.cols {
let mut dot = 0;
for k in 0..self.cols {
dot += self.get(i, k) * other.get(k, j);
}
new_mat.set(i, j, dot);
}
}
return new_mat;
}
}
fn main() {
// Lia includes macros that simplify handling Lia functions and values in Rust.
let result: LiaAny = call!(multiply_matrices());
cast!(let num: i32 = result);
assert!(num == 13);
}
使用Lia
请参阅 lia-tests/src/lib.rs
以了解示例用法。目前Lia仍处于早期阶段,需要nightly版本进行构建,因此尚未准备好投入生产,但我鼓励您尝试它,并在问题/PR中提出任何建议,或者通过[email protected]给我发邮件。
要在自己的代码中使用Lia,首先使用 multirust override nightly
将Rust切换到nightly版本。然后在 Cargo.toml
中添加以下内容
[dependencies]
lia = "0.1.0"
lia-plugin = "0.1.0"
然后在您想要使用Lia的文件中
#![feature(plugin, box_syntax)]
#![plugin(lia_plugin)]
#[macro_use]
extern crate lia;
use lia::runtime::*;
lia! { ... }
动机
当今编程语言面临的最大挑战之一是互操作性。随着编程语言的激增和针对应用领域的专业化,许多程序员需要能够有效地跨语言工作。例如
- Python有大量的绑定到C、CUDA等,用于对数值或图像数据进行高效操作。这通过库如 numpy 和 scipy 实现。
- JavaScript最近出现了 React,它使得HTML和JavaScript在新的JSX格式中可以无缝集成(这与多年前PHP流行的过程式HTML生成非常相似)。
- 近年来,出现了一些领域特定的语言,例如 Halide,它们以不同级别的互操作性出现在现有的通用编程语言中。
两种语言之间的互操作性问题时,可以通过共享一个通用的类型系统来解决。即使两种语言有不同的运行时和不同的语法,如果它们在类型级别上进行合并,那么它们协同工作将更容易。例如,Terra通过修改Lua以具有类似C语言的代码生成功能,然后使用LuaJIT的FFI作为两种语言之间类型系统的粘合剂来解决此问题。然而,这种方法需要两种语言(Lua和Terra)有独立的运行时,并依赖于Lua的类型和实际C的类型之间脆弱的类型粘合层。
Lia采用不同的方法:不是将高级解释运行时和低级编译运行时分开,我们将Lia代码编译成Rust代码。这样,它共享相同的类型系统和运行时,并实现了两种语言之间的无缝互操作性。Lia通过消除生命周期/内存管理以及静态类型,提高了对Rust的抽象层次。
系统描述
type LiaAny = Rc<RefCell<Rc<RefCell<Box<Any>>>>>;
Lia中的所有值(整数、闭包等)都具有类型LiaAny
。我们将了解上述类型的组成部分,以理解Lia的抽象层。
消除内存管理
通过引用计数(类型Rc
)将管理内存/生命周期的负担从编译时的程序员转移到运行时的程序。理想情况下,这将是一个垃圾回收指针(如这个),当产生循环时不会泄漏内存,但可以稍后实现。`Rc`包装一个`RefCell`以允许封装的值被修改。
简单实现一个运行时管理的类型可能只是Rc<RefCell<T>>
。然而,我们需要另一层间接引用,因为变量可以被重新分配。考虑以下示例
var x = 1;
x = 2;
在简单实现中,这可以编译为
let mut x = alloc(1);
x = alloc(2);
然而,我们不希望依赖于Rust变量的可变性(特别是名称,例如x
,而不是值)来实现Lia的可变性。当我们考虑到闭包和可变性的交互时,就会出现这种情况
var x = 1;
(function(){ x = 2; })();
x = 3;
我们的上述方案会编译成
let mut x = alloc(1);
(move || { x = alloc(2); })();
x = alloc(3);
编译器将在第三行上抱怨x
已经被移动到闭包中。相反,我们让每个Rust名称对应一个包含值的槽位。上述示例实际上编译成大约
let x = alloc(alloc(1));
let x_copy = x.clone();
(move || { *x_copy.borrow_mut() = alloc(2); })();
*x.borrow_mut() = alloc(3);
编译器通过找到Lia闭包封闭的变量并创建副本来创建副本。总之:为了同时拥有运行时管理的内存和闭包,每个值都被两层Rc<RefCell<...>
。
消除静态类型
为了使语言动态类型化,上述讨论的槽位中的每个底层值都具有类型Box<Any>
。`Any`类型表示任何可能的值,可以在运行时进行检查/转换。`Box`确保类型是固定大小(类似于OCaml运行时,其中每个值都是指针的大小)。
将类型注入到泛型类型中使用 lia::runtime::alloc
进行所有适当的分配。类型转换使用 cast!
宏。转换语义取决于被转换的类型是拥有还是借用。标准库中的 Any
类型提供了向下转换方法,在正确转换时返回封装值的引用。因此,当将 LiaAny
转换为引用类型时,cast!
宏仅使用给定的引用。但是,如果您想转换为一个拥有类型,则引用值将被克隆。这是因为从 Lia 运行时获取所有权是不安全的,因为 Lia 对任何给定时间点的值引用的数量不提供保证。
待办事项列表
- 添加 JIT 以进行动态执行
- 在 Lia 级别(Spans)和 Rust 级别(代码映射?)上使错误清晰
- 导入剩余的语言结构
- 循环
- 其他二进制运算符
- 模块
依赖关系
~0–1.4MB
~30K SLoC