1个不稳定版本
0.1.0 | 2022年5月13日 |
---|
#64 在 可视化
每月 22次下载
1.5MB
6.5K SLoC
包含 (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
RustViz 是一个工具,可以从简单的Rust程序生成交互式可视化,帮助用户更好地理解Rust的 生命周期和借用 机制。
RustViz 是密歇根大学 编程未来实验室 的项目。
它看起来像什么?
RustViz 生成带有图形指示器的 SVG 文件,这些文件可以与 mdbook 集成,以渲染Rust程序中与所有权和借用相关事件的交互式可视化。下面是一个可视化的示例视图
您可以在 我们的草案论文 中了解更多信息。请注意,关于生成可视化的部分已过时,请参见下文。
用法
RustViz 能够为用户标记的简单Rust程序生成可视化(尽管存在某些限制),但我们目前没有尝试自动生成可视化。在本节中,我们将展示如何生成我们提供的示例的SVG渲染。
RustViz 需要安装 Rust、Cargo 和 mdbook。一旦安装了所有上述先决条件,请进入 /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()
和两个变量 x
和 y
视为资源访问点 (RAP)。String::from()
和 x/
y
分别对应于 RAP 的 ResourceAccessPoint::Function
和 ResourceAccessPoint::Owner
。
在 main.rs 中,我们在第 1 和第 2 行的 BEGIN
和 END
注释之间定义这些 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 a
和 StaticRef b
。另一方面,一个具有成员变量 x
和 mut y
的不可变实例的某些结构,可能被注释为 Struct a{x, mut y}
。
需要注意的是
- 所有定义 必须 位于
BEGIN
和END
之间- 所有定义 必须 按照它们在源代码中声明的顺序进行定义
- 所有定义 必须 由单个分号隔开
- 在 RAP 定义中的每个字段 必须 由空格隔开
运行 view_examples.sh 一次后,我们应该有以下的文件结构
string_from_move_print
├── input
│ └── annotated_source.rs
├── main.rs
└── source.rs
接下来,我们使用 ExternalEvent
注释代码,以描述 Rust 的移动、借用和丢弃语义。在 string_from_move_print 中,我们有四个这样的事件
- 资源从
String::from()
移动到x
- 资源从
y
移动到x
- 与
x
绑定的资源丢弃 - 与
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) {..} |
注意
GoOutOfScope
、InitRefParam
和InitOwnerParam
需要一个在变量定义
部分之前定义的单个参数。(例如:// !{ GoOutOfScope(x) }
)- 所有其他事件都需要两个参数,
a
和b
,它们可以是已定义的(例如:Owner a
)或未定义的(None
)。
可以使用 None
类型作为 <to>
参数(例如:Move(a->None)
)来指定将资源移动到函数调用者。
- 所有使用
Struct
字段的都必须在父结构体名称之前。例如:a.b = 1;
可以注解为Move(None->a.b)
,其中a
是父结构体,b
是字段。)
可视化局限性
一些功能仍在开发中。目前,我们的限制包括
- 无分支逻辑
- 无循环
- 无显式生命周期注解
依赖项
~3–4MB
~84K SLoC