1个不稳定版本
使用旧Rust 2015
0.1.0 | 2016年6月8日 |
---|
#809 在 编程语言
9KB
175 行
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
代表任何可能的类型值,并且可以在运行时进行检查/强制转换。《code>Box<...> 确保类型是固定大小(类似于 OCaml 运行时,其中每个值都是指针的大小)。
将类型铸造成泛型类型使用的是 lia::runtime::alloc
,该类型负责所有正确的分配。铸造成其他类型使用的是 cast!
宏。铸造成语义取决于被转换的类型是拥有者还是借用者。标准库中的 Any
类型提供了向下转换的方法,当正确转换时,返回封装值的引用。因此,当将 LiaAny
转换为引用类型时,cast!
宏仅使用给定的引用。然而,如果您想要将其转换为一个拥有者类型,则引用值会被克隆。这是因为从 Lia 运行时获取所有权是不安全的,因为 Lia 对任何时间点的值的引用数量不提供保证。
待办事项列表
- 为动态执行添加 JIT
- 在 Lia 级别(Spans)和 Rust 级别(代码映射?)上使错误清晰
- 导入剩余的语言结构
- 循环
- 其余的二元运算符
- 模块