#借用 #生命周期 #所有权 #变量 #交互式 #生成

app rustviz

一个工具,允许教师生成交互式时间线,以显示Rust代码示例中每个变量的所有权和借用事件

1个不稳定版本

0.1.0 2022年5月13日

#64可视化

每月 22次下载

MIT 许可证

1.5MB
6.5K SLoC

Rust 3.5K SLoC // 0.2% comments JavaScript 3K SLoC // 0.1% comments Shell 81 SLoC // 0.2% comments

包含 (WOFF字体,99KB) fontawesome-webfont.woff、(WOFF字体,78KB) fontawesome-webfont.woff2、(WOFF字体,45KB) open-sans-v17-all-charsets-300.woff2、(WOFF字体,41KB) open-sans-v17-all-charsets-300italic.woff2、(WOFF字体,45KB) open-sans-v17-all-charsets-600.woff2、(WOFF字体,43KB) open-sans-v17-all-charsets-600italic.woff2 以及 7 个更多文件。

RustViz

Build Status

RustViz 是一个工具,可以从简单的Rust程序生成交互式可视化,帮助用户更好地理解Rust的 生命周期和借用 机制。

RustViz 是密歇根大学 编程未来实验室 的项目。

它看起来像什么?

RustViz 生成带有图形指示器的 SVG 文件,这些文件可以与 mdbook 集成,以渲染Rust程序中与所有权和借用相关事件的交互式可视化。下面是一个可视化的示例视图

alt tag

您可以在 我们的草案论文 中了解更多信息。请注意,关于生成可视化的部分已过时,请参见下文。

用法

RustViz 能够为用户标记的简单Rust程序生成可视化(尽管存在某些限制),但我们目前没有尝试自动生成可视化。在本节中,我们将展示如何生成我们提供的示例的SVG渲染。

RustViz 需要安装 RustCargomdbook。一旦安装了所有上述先决条件,请进入 /rustviz_mdbook 并运行脚本

~/rustviz/rustviz_mdbook$ ./view_examples.sh

您的输出可能类似于以下内容

Generating visualizations for the following examples:
building copy...
building hatra1...
building hatra2...
building func_take_ownership...
building func_take_return_ownership...
2021-01-19 12:36:13 [INFO] (mdbook::book): Book building has started
2021-01-19 12:36:13 [INFO] (mdbook::book): Running the html backend
Serving HTTP on :: port 8000 (http://[::]:8000/) ...

如果您观察到此输出,则表示您已成功生成 Rust 可视化示例!现在打开您的浏览器,并导航到 http://127.0.0.1:8000/。您应该可以通过从左侧栏中选择它们来单独查看示例。要查看可视化,请点击代码块右上角的切换按钮。

太好了,这就是您如何生成和查看使用 RustViz 创建的可视化。现在让我们从头开始创建一个吧!

分步指南

在本节中,我们将深入探讨创建一个示例,string_from_move_print。首先,请注意我们需要运行的示例文件结构

string_from_move_print
├── input
   └── annotated_source.rs
└── source.rs

source.rs 包含了我们希望将其渲染成图像的未修改源代码

fn main() {
    let x = String::from("hello");
    let y = x;
    println!("{}", y);
}

在此示例中,"hello" 将一个字符串("hello")移动到 x,然后 x 的资源被移动到 y。随后,println! 将一条消息输出到 io::stdout,而不移动资源。

接下来,让我们熟悉一下在 main.rs 中使用的语法。RustViz 工具将任何内存资源的所有可能所有者、引用或输入定义为 ResourceAccessPoint。在本例中,我们将函数 String::from() 和两个变量 xy 视为资源访问点 (RAP)。String::from()x/y 分别对应于 RAP 的 ResourceAccessPoint::FunctionResourceAccessPoint::Owner

main.rs 中,我们在第 1 和第 2 行的 BEGINEND 注释之间定义这些 RAP

/*--- BEGIN Variable Definitions ---
Owner x; Owner y;
Function String::from();
--- END Variable Definitions ---*/

定义头现在可以通过运行 view_examples.sh 自动生成一次。如果生成的头文件中出现了任何错误信息,您可以手动编辑它,参考以下文档。

以下为每个 ResourceAccessPoint 枚举的格式,其中以 ':' 开头的字段表示可选字段

ResourceAccessPoint Usage --
    Owner <:mut> <name>
    MutRef <:mut> <name>
    StaticRef <:mut> <name>
    Struct <:mut> <name>{<:mut> <member_1>, <:mut> <member_2>, ... }
    Function <name>

或者,一些代码 let mut a = 5;let b = &a; 分别对应于 Owner mut aStaticRef b。另一方面,一个具有成员变量 xmut y 的不可变实例的某些结构,可能被注释为 Struct a{x, mut y}

需要注意的是

  1. 所有定义 必须 位于 BEGINEND 之间
  2. 所有定义 必须 按照它们在源代码中声明的顺序进行定义
  3. 所有定义 必须 由单个分号隔开
  4. 在 RAP 定义中的每个字段 必须 由空格隔开

运行 view_examples.sh 一次后,我们应该有以下的文件结构

string_from_move_print
├── input
   └── annotated_source.rs
├── main.rs
└── source.rs

接下来,我们使用 ExternalEvent 注释代码,以描述 Rust 的移动、借用和丢弃语义。在 string_from_move_print 中,我们有四个这样的事件

  1. 资源从 String::from() 移动到 x
  2. 资源从 y 移动到 x
  3. x 绑定的资源丢弃
  4. y 绑定的资源丢弃

我们可以在结构化注释中指定事件,如下所示

/* --- BEGIN Variable Definitions ---
Owner x; Owner y;
Function String::from();
 --- END Variable Definitions --- */
fn main() {
    let x = String::from("hello"); // !{ Move(String::from()->x) }
    let y = x; // !{ Move(x->y) }
    println!("{}", y); // print to stdout!
} /* !{
    GoOutOfScope(x),
    GoOutOfScope(y)
} */

每个事件都定义在其发生的那一行,并在 !{} 定界符内。

事件可以在块注释中注释;然而,块 必须 从事件发生的行开始。此外,在 !{} 定界符内的事件 必须 由单个逗号隔开,并且每个都必须遵循以下格式

ExternalEvents Usage:
    Format: <event_name>(<from>-><to>)
        e.g.: // !{ PassByMutableReference(a->Some_Function()), ... }
    Note: GoOutOfScope and InitRefParam require only the <from> parameter
        e.g.: // !{ GoOutOfScope(x) }

有关可用的 ExternalEvent 的列表,请参阅 附录

好了!剩下的只是运行程序。只需导航到 src 并运行

cargo run string_from_move_print

现在您的文件夹应该看起来像这样

string_from_move_print
├── input
│   └── annotated_source.rs
├── main.rs
├── source.rs
├── vis_code.svg
└── vis_timeline.svg

恭喜!您已成功生成了您的第一个可视化!最后一步,将您的示例名称添加到 view_examples.sh 下的 targetExamples,然后从 rustviz_mdbook 运行脚本,在浏览器中查看。

附录

ExternalEvent 使用

事件 用法
绑定(a) 绑定或赋值。
例如:let a = 1;
复制(a->b) 将资源 a 复制到变量 b。在这里,a 实现了 Copy 特性。
移动(a->b) 将资源 a 移动到变量 b。在这里,a 实现了 Move 特性。
注意:将资源移动到 None(即:Move(a->None))用于表示将资源移动到调用函数。
静态借用(a->b) a 的不可变引用赋给 b
例如: let b = &a;
可变借用(a->b) a 的可变引用赋给 b
例如: let b = &mut a;
静态释放(a->b) 结束引用变量 a 的非词法生命周期,并将资源返回给其所有者 b
可变释放(a->b) 结束引用变量 a 的非词法生命周期,并将资源返回给其所有者 b
通过静态引用传递(a->b) 将变量 a 的不可变引用传递给函数 b。不要与静态借用混淆。
通过可变引用传递(a->b) 将变量 a 的可变引用传递给函数 b。不要与可变借用混淆。
超出作用域(a) 结束变量 a 的词法生命周期。
初始化引用参数(a) 初始化某些函数的参数 a,它是一个引用。
例如: some_fn(a: &String) {..}
初始化所有者参数(a) 初始化某些函数的参数 a,它拥有资源。
例如: some_fn(a: String) {..}

注意

  1. GoOutOfScopeInitRefParamInitOwnerParam 需要一个在 变量定义 部分之前定义的单个参数。(例如:// !{ GoOutOfScope(x) }
  2. 所有其他事件都需要两个参数,ab,它们可以是已定义的(例如:Owner a)或未定义的(None)。

可以使用 None 类型作为 <to> 参数(例如:Move(a->None))来指定将资源移动到函数调用者。

  1. 所有使用 Struct 字段的都必须在父结构体名称之前。例如: a.b = 1; 可以注解为 Move(None->a.b),其中 a 是父结构体,b 是字段。)

可视化局限性

一些功能仍在开发中。目前,我们的限制包括

  • 无分支逻辑
  • 无循环
  • 无显式生命周期注解

依赖项

~3–4MB
~84K SLoC