1个不稳定版本

使用旧的Rust 2015

0.1.0 2016年6月8日

#553编程语言


lia-plugin 中使用

Apache-2.0/MIT

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等,用于对数值或图像数据进行高效操作。这通过库如 numpyscipy 实现。
  • 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