14个版本

0.5.0 2021年4月22日
0.4.0 2019年8月3日
0.3.7 2018年12月5日
0.3.6 2018年9月23日
0.2.0 2017年7月29日

#942 in 开发工具

Download history 1/week @ 2024-03-12 2/week @ 2024-03-26 19/week @ 2024-04-02

每月 70次下载

MIT 许可证

63KB
1K SLoC

运行小Rust代码片段

离开Cargo的舒适(和限制)

Cargo是一种在Rust中构建程序和库的好方法,具有版本化的依赖关系。那些在C++的开发“狂野西部”实践中工作过的人发现这特别令人安心,这也是Rust生态系统的一个核心优势。

然而,它并不是为了使运行小测试程序变得简单——你必须创建一个包含你希望与之交互的所有依赖关系的项目,然后编辑 src/main.rs 并运行 cargo run。一个有用的提示是创建一个包含你的小程序的 src/bin 目录,然后使用 cargo run --bin NAME 来运行它们。但还有更好的方法;如果你有这样的项目(比如说叫做'cache'),那么以下调用将编译并链接一个程序,针对那些依赖关系(rustc 是一个不同寻常的智能编译器)

$ rustc -L /path/to/cache/target/debug/deps mytest.rs

当然,每当添加新的依赖项或编译器更新时,你都需要手动在 cache 项目上运行 cargo build

runner 工具帮助自动化这种模式。它还支持 代码片段,这些片段格式类似于Rust文档示例。

$ cat print.rs
println!("Hello, World!");

$ runner print.rs
Hello, World!

这基本上遵循与Rust文档中找到的 doc-test 代码片段相同的规则,因此 runner 允许你将这些代码片段复制到编辑器中并直接运行它们(我在我最喜欢的编辑器中将'run' 绑定到 runner ... 上。)

您可以在代码片段中使用 ? 来代替普遍存在的且令人讨厌的 unwrap,因为模板代码将代码封装在一个返回 Result<(),Box<Error+Sync+Send>> 的函数中,它可以与任何错误返回兼容。

存在一个特殊变量 args,包含传递给程序的任何参数

$ cat hello.rs
println!("hello {}",args[1]);
$ runner hello.rs dolly
hello dolly

在 Unix 平台上,您甚至可以添加一个 'shebang' 行来调用运行器

$ cat hello
#!/usr/bin/env runner
println!("Hello, World!");

$ ./hello
Hello, World!

runner 添加必要的模板代码,并在 ~/.cargo/.runner/bin 中创建一个适当的 Rust 程序,前面有一个序言,最初是

#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(unused_macros)]
use std::{fs,io,env};
use std::fs::File;
use std::io::prelude::*;
use std::path::{PathBuf,Path};
use std::collections::HashMap;
use std::time::Duration;
use std::thread;

macro_rules! debug {
    ($x:expr) => {
        println!("{} = {:?}",stringify!($x),$x);
    }
}

第一次调用 runner 后,这个文件在 ~/.cargo/.runner/prelude 中;您可以用 runner --edit-prelude 命令稍后编辑它。

debug! 节省了输入时间:debug!(my_var) 等同于 println!("my_var = {:?}",my_var)

作为一个实验性功能,runner 还会对 rustc 错误进行一些整理。它们通常非常好,但涉及完全限定的类型名称。它将 std:: 引用简化为更简单的东西。

这是一个 Java 程序员会很容易编写的代码片段 - 明确声明类型,并假设重要的动词是 "set"。

$ cat testm.rs
let mut map: HashMap<String,String> = HashMap::new();
map.set("hello","dolly");
$  runner testm.rs
error[E0599]: no method named `set` found for type `HashMap<String, String>` in the current scope
  --> /home/steve/.cargo/.runner/bin/testm.rs:24:9
   |
24 |     map.set("hello","dolly");
   |         ^^^
   |
   = help: did you mean `get`?

由于我们在 Rust 中非常 非正式,所以我们不希望完整地拼写类型(如通过运行 -S 可见),适当的做法是:std::collections::HashMap<std::string::String, std::string::String>

添加外部包

如您所见,runner 主要关于玩一些小的代码片段。默认情况下,它动态链接代码片段,这要快得多。

静态选项更为方便。您可以轻松创建一个包含一些常用包的静态缓存

$ runner --add "time json regex"

您可以添加尽可能多的包 - 可用依赖项的数量不会减慢链接器的速度。之后,您可以在代码片段中引用这些包。请注意,默认情况下,自 0.4.0 版起,runner 使用 2018 版本。

// json.rs
use json;

let parsed = json::parse(r#"

{
    "code": 200,
    "success": true,
    "payload": {
        "features": [
            "awesome",
            "easyAPI",
            "lowLearningCurve"
        ]
    }
}

"#)?;

println!("{}",parsed);

然后构建静态版本并运行(任何额外的参数都会传递给程序)

$ runner -s json.rs
{"code":200,"success":true,"payload":{"features":["awesome","easyAPI","lowLearningCurve"]}}

一个方便的新功能是 "参数行" - 如果 json.rs 的第一行是

//: -s

然后在 "//:" 之后指定的任何 runner 参数将与命令行参数合并。现在可以通过 runner json.rs 直接调用。最好将任何特殊的构建说明保留在文件本身中,这意味着绑定到 runner FILE 的编辑器运行操作可以在所有情况下工作。

runner 提供了管理静态缓存的多种实用工具。您可以使用 runner --edit 来编辑静态缓存 Cargo.toml,并使用 runner --build 重建缓存。运行 runner update 将更新缓存中的所有依赖项,而 runner update package 将更新一个 特定的 包 - 之后跟着 build 如前所述。

缓存是为调试和发布模式构建的,因此您可以使用 -sO 在发布模式下构建代码片段。同时,也构建了缓存的文档,使用 runner --doc 可以在浏览器中打开该文档。(本地文档总是很好的,尤其是在带宽受限的情况下。)

如果您想查看特定 NAME 的文档,则可以使用 runner --doc NAME。请记住,Rust 生成的文档有一个快速的离线可搜索索引!

--crates 命令也有一个可选参数;如果没有参数,它将列出 runner 所知的所有软件包及其版本。如果指定了名称,它将使用精确匹配。

$ runner --crates yansi
yansi = "0.3.4"

在这里您可以提供多个软件包名称;如果指定了 --verbose (-),则会列出这些软件包的依赖项。

- 标志仅编译程序或片段,并将其复制到 ~/.cargo/bin。而 - 标志仅运行程序,该程序必须已经编译过,无论是通过 - 明确编译,还是默认操作隐式编译。

当然支持纯 Rust 源文件(其中已经有 fn main),但您需要使用 --extern (-) 标志将外部软件包从静态缓存中引入。

一个有用的技巧 - 如果您想查看已下载的软件包的 Cargo.toml 以了解依赖项和功能,则此命令会为您打开它

favorite-editor $(runner -P some-crate)/Cargo.toml

Rust 命令行工具

有几个受 Perl 启发的功能。使用 - 标志可以编译并评估一个 表达式。您可以将其用作一个不同寻常的严格桌面计算器

$ runner -e "10 + 20*4.5"
error[E0277]: the trait bound `{integer}: Mul<{float}>` is not satisfied
  --> temp/tmp.rs:20:22
   |
20 |     let res = 10 + 20*4.5;
   |                      ^ no implementation for `{integer} * {float}`

同样,您必须说 1.2f64.sin(),因为 1.2 有歧义类型。

(请注意,默认情况下,特质 std::ops::Mul简化形式 显示)

--expression 非常有用,如果您想快速了解 Rust 如何评估一个表达式,我们进行调试打印以实现最大灵活性。

$ runner -e 'PathBuf::from("bonzo.dog").extension()'
Some("dog")

(这是因为我们在运行器序言中有一个 use std::path::PathBuf。)

但是,在 Windows 上这不会工作,因为 引号 太过于复杂。因此,runner 重新使用了一些旧版本的 AWK 在 Windows 上的技巧。我们只能为可能包含空格的参数使用双引号,但其中单引号将被转换为双引号。

c:> runner -e "PathBuf::from('bonzo.dog').extension()"
Some("dog")

所以,在这些示例中,如果您需要在 Rust 表达式中引用字符串,请记住在 Windows 上它是相反的。

-i(或 --iterator)评估迭代器表达式并打印结果

$ runner -i '(0..5).map(|i| (10*i,100*i))'
(0, 0)
(10, 100)
(20, 200)
(30, 300)
(40, 400)

这些命令都支持额外的命令行参数,所以

$ runner -i 'env::args().enumerate()' one 'two 2' 3
(0, "/home/steve/.cargo/.runner/bin/tmp")
(1, "one")
(2, "two 2")
(3, "3")

最后,-n(或 --lines)评估标准输入中每一行的表达式

$ echo "hello there" | runner -n 'line.to_uppercase()'
"HELLO THERE"

-x 标志(--extern)允许您将 extern crate 插入到您的代码片段中。这对于这些单行快捷方式尤其有用。例如,我的 easy-shortcuts 包有几个辅助函数。在运行这些示例之前,首先使用 runner --add easy-shortcuts 将其加载到静态包中,然后使用 runner -C easy-shortcuts 动态编译它。

$ runner -xeasy_shortcuts -e 'easy_shortcuts::argn_err(1,"gimme an arg!")' 'an arg'
"an arg"
$ runner -xeasy_shortcuts -e 'easy_shortcuts::argn_err(1,"gimme an arg!")'
/home/steve/.cargo/.runner/bin/tmp error: no argument 1: gimme an arg!

这同样适用于 --iterator

$ runner -xeasy_shortcuts -i 'easy_shortcuts::files(".")'
"json.rs"
"print.rs"

对于像这样的长包名,您可以定义 别名

$ runner --alias es=easy_shortcuts
$ runner -xes -e 'es::argn_err(1,"gimme an arg!")'
...

默认情况下,runner --e 执行动态链接,并且存在已知的限制。通过使用 --static,您可以针对编译为静态库的包评估表达式。所以,假设我们已经在静态缓存中有 time(使用 runner --add time 就可以做到这一点)。

$ runner -s -xtime -e "time::now()"
Tm { tm_sec: 34, tm_min: 4, tm_hour: 9, tm_mday: 28, tm_mon: 6, tm_year: 117,
tm_wday: 5, tm_yday: 208, tm_isdst: 0, tm_utcoff: 7200, tm_nsec: 302755857 }

--wild-M)类似于 -x,但将包的所有符号都引入作用域。这通常不适用于常规代码,但它可以使命令行更短 - 最后一个例子变成了(注意如何组合短标志)。

$ runner -sXtime -e "now()"
...

-M--macro)也类似于 -x,但它将 #[macro_use] 预处理器指令添加到 'extern crate' 前面。考虑一下非常酷的 r4 包,它提供了列表推导。首先使用 runner --add r4 将其加载到静态缓存中,然后我们可以说

$ runner -s --macro r4 -i 'iterate![for x in 0..4; yield x]'
0
1
2
3

如果包可以动态链接,这些小片段将更快,所以使用 runner --C r4 在动态链接中构建共享库后,您可以运行这个命令而不需要 -s

$ runner -Xto_vec -Mr4 -e 'iterate![for i in 0..2; for j in 0..2; yield (i,j)].to_vec()'
[(0, 0), (0, 1), (1, 0), (1, 1)]

(在这个阶段,命令行已经变得足够复杂,使用一个小片段在合适的编辑器中进行编辑会更好。)

使用 -e-n-,您可以通过 --prepend 指定一些初始代码。

$  runner -p 'let nums=0..5' -i 'nums.clone().zip(nums.skip(1))'
(0, 1)
(1, 2)
(2, 3)
(3, 4)

如果您能够使用动态链接,那么 runner 可以使测试模块交互式变得容易。这样,您就可以获得完全交互式解释器(REPL)的大部分好处。

$ cat universe.rs
pub fn answer() -> i32 {
    42
}
$ runner -C universe.rs
building crate 'universe' at universe.rs
$ runner -xuniverse -e "universe::answer()"
42

这提供了一种方法来玩转大预定义字符串。

$ cat > text.rs
pub const TEXT: &str = "possibly very long string";
$ runner -C text.rs
building crate 'text' at text.rs
$ runner -Xtext -e 'TEXT.find("long")'
Some(14)

编译Rust文档示例

考虑 filetime 包的示例

use std::fs;
use filetime::FileTime;

let metadata = fs::metadata("runner.rs").unwrap();

let mtime = FileTime::from_last_modification_time(&metadata);
println!("{}", mtime);

let atime = FileTime::from_last_access_time(&metadata);
assert!(mtime < atime);

// Inspect values that can be interpreted across platforms
println!("{}", mtime.seconds_relative_to_1970());
println!("{}", mtime.nanoseconds());

// Print the platform-specific value of seconds
println!("{}", mtime.seconds());

在执行 runner --add filetime 后,此包已在您的静态缓存中。并且 runner --doc filetime 将提供其本地文档。

然而,它不能直接编译,因为 use std::fs 已经在 runner 预备知识中。

因此我们需要说:

$ runner -s --no-prelude filetime.rs
1506778536.945440909s
1506778536
945440909
1506778536

或者如果您很着急: runner -sN filetime.rs

像往常一样,您总是可以将这些参数放在第一行注释中,例如 "//: -sN"。

动态编译包

对于动态链接的情况,提供这样的体验会很好,因为它更快。实际上,还有一个动态缓存,但是对外部包动态链接的支持非常基础。它对于没有外部依赖的包工作得很好,例如,在动态缓存中创建一个 libjson.so

$ runner -C json

然后您可以运行 json.rs 示例,而不需要 -s

--compile 动作接受三种类型的参数:

  • 一个已加载并已知于 Cargo 的包名
  • Cargo 目录
  • Rust 源文件 - 包名是文件名,不带扩展名。

目前,动态链接不是 Rust 工具的首选。因此,我们必须在没有 Cargo 的帮助下构建更复杂的库。(以下假设您已经为 Cargo 项目引入了 regex,因此 Cargo 缓存已填充,例如通过 runner --add regex

runner -C memchr
runner -C aho-corasick
runner -C utf8-ranges
runner -C lazy_static
runner -xlazy_static -C thread_local
runner -C regex-syntax
runner -C regex

这个脚本强调了没有 Cargo 的 Rust 生活的极大烦恼。我们必须跟踪依赖项,确保编译中启用了正确的默认功能,并对直接链接到 libc 的包进行特殊处理。

然而,结果是值得的。编译第一个 regex 文档示例

use regex::Regex;
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
assert!(re.is_match("2014-01-01"));

使用静态构建(-),我在这台机器上得到 0.56 秒,使用动态链接为 0.25 秒。

目前动态链接有一些限制 - "no std"(且不提供关闭此功能的特性)的包不能编译。另外,请记住,所有 runner -C 的调用最终都会在名为 'dynamic cache' 的目录中放置共享库 - 例如,只能有一个名为 'libs' 的包。

依赖关系

~1–2MB
~40K SLoC