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 开发工具
每月 70次下载
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