#literals #smart-pointers #source #state #mutable #snapshot #value

已删除 垃圾

🗑️ litter 使你的字面量可通过智能指针变为可变,指向源代码。

0.20220802.0-dev 2022年8月3日
0.20220725.0-dev 2022年7月25日
0.20220724.0-dev 2022年7月24日
0.0.0-已删除 2022年8月8日

#79 in #smart-pointers

MIT/Apache

30KB
476 代码行

🗑️ litter 使你的字面量可通过智能指针变为可变,指向源代码。

这些可用于快照测试,或将状态内联到脚本中的基本方式。这仅适用于通过 Cargo 运行的代码,因为它依赖于 CARGO_ 环境变量在需要写入时定位源代码。实现使用 #[track_caller] 属性(无宏)。

待办事项

  • 实现简单的非同步写入
  • 实现更好的写入
  • 支持外部数据(?)
  • 未来工作?!上述内容几乎都没有实际实现!
  • 使用可失败替代方案而不是恐慌,包括在源文件不存在时写入值的情况(它们仍然可以保存在内存中)。
  • 支持外部数据,而不仅仅是内联。
  • 虽然值只被读取,但我们可以保留其状态的 Weak Arc 并让它被释放。一旦写入,我们就需要将其保留在内存中,因此泄露了一个引用。

基本用法

类型 litter::Litter 封装了字面量值及其在源代码中的位置信息,允许对其进行修改,同时原始脚本文件中的更改也会反映出来。支持的字面量类型包括整数(122_usize-1i16)、浮点数(1.52e6f64)、布尔值(truefalse)、静态字符串("hello"r##"world##")、静态字节字符串(b"one two \x12"br"hell\x00")。这些类型由 litter::Literal 特性描述。

.edit() 修改 LiteralLitter 会生成一个 LitterHandle。它实现了 DerefDerefMut,暴露了内部值,以及各种其他特性。如果内部值被修改,它将在 Litter 被丢弃时写回文件。

以下是一个基本示例,每次脚本运行时都会修改字符串

use litter::LiteralExt;

fn main() {
    let mut p = "and I say hello!".edit();
    *p += " hello!";
}
    let mut p = "and I say hello! hello!".edit();
    *p += " hello!";
    let mut p = "and I say hello! hello! hello!".edit();
    *p += " hello!";

组合

Litter 构造函数使用 #[track_caller] 而不是宏,因此可以将其封装以创建自己的函数来修改字面量,但有一个重要的限制:要修改的字面量必须是函数调用中作为第一个参数出现的字面量。因此 f(x, "literal") 是可行的,但 f(2, "literal") 则不行。因此,我们通常倾向于将字面量作为函数的第一个参数。

例如,我们可以使用此功能来实现类似于 expect_test 风格的快照测试。

#[test]
fn test_the_ultimate_question() {
    assert_eq_u64(42, 6 * 9);
}

#[track_caller] // <- in order to look for literal at this function's call site instead
fn assert_eq_u64(expected: u64, actual: u64) {
    let expected = litter::new(expected);

    if expected != actual {
        if std::env::get("UPDATE_EXPECT").unwrap_or("0") != "0" {
            *expected = actual; // <- updated in memory, written to source at end of scope
        } else {
            panic!("\
                Expected {expected:?} but actual value was {actual:?}.\n\
                \n\
                To update the expected value, run this again with UPDATE_EXPECT=1.\
            ");
        }
    }
}

运行此测试将最初因我们的 panic 消息而失败,但再次运行带有 UPDATE_EXPECT=1 cargo test 的测试将通过并更新我们的源代码以反映正确值

#[test]
fn test_the_ultimate_question() {
    assert_eq_u64(54, 6 * 9);
}

尽管您不需要自己编写此特定函数:一个泛型版本包含在 litter::assert_eq(literal, actual) 中。

序列化

如果您启用了 jsonyamlpostcardtoml 功能,字符串和字节的 LiteralExt 将获得相应的 .edit_json().edit_yaml().edit_toml().edit_postcard() 方法,可用于内联实现 serde::{ Serialize, Deserialize } 的非原始值,以及 DebugCloneDefault。 (如果您的类型没有实现 Default,您可以考虑将其包装在 Option<...> 中。)

作为一个方便的特殊情况,如果字面字符串为空但反序列化失败,则将返回类型的 Default::default() 值。任何其他(反)序列化错误将导致程序崩溃。

fn main() {
    // empty string interpreted as default, like "[]"
    let json_vec = "".edit_json::<Vec<usize>>();
    json_vec.push(json_vec.len());

    // empty byte string interpreted as default, like b"\x00"
    let postcard_vec = b"".edit_postcard::<Vec<usize>>();
    postcard_vec.push(postcard_vec.len());
}
    let json_vec = "[0]".edit_json::<Vec<usize>>();
    json_vec.push(json_vec.len());

    let postcard_vec = b"\x01\x00".edit_postcard::<Vec<usize>>();
    postcard_vec.postcard_vec(postcard_vec.len());
    let json_vec = "[0, 1]".edit_json::<Vec<usize>>();
    json_vec.push(json_vec.len());

    let postcard_vec = b"\x02\x00\x01".edit_postcard::<Vec<usize>>();
    postcard_vec.push(postcard_vec.len());

如果没有指定类型或无法推断类型,则 .edit_json().edit_yaml().edit_toml() 方法默认为动态值(serde_json::Valueserde_yaml::Valuetoml::Value)。但是 .edit_postcard() 总是需要已知类型(它不是自我描述的,也不能动态处理)。

对于文本格式,将编码到字符串字面量将是格式化的/冗长的,而将编码到字节字符串字面量将使用紧凑的表示。 (这显然与仅二进制格式的 postcardrkyv 无关。)

性能和可靠性

这明显是为了方便,而不是为了性能。它应该足够快,可以用于测试快照或脚本中的某些状态的转储,但您肯定不会想用它来构建高吞吐量的Web服务器。对每个字面量的访问都由 RwLock 控制,如果并发使用可能会阻塞。

只有在需要将更改后的值写回时才会访问文件系统,所以如果值从未被修改,文件系统就不会被访问,在这种情况下,程序可以在没有源文件的不同系统上正常运行。

如果您的程序多个副本同时运行并尝试修改同一文件,则可能会出现逻辑错误。

许可证

litter 版权所有 Jeremy Banks,根据熟悉的 MIT OR Apache-2.0 许可。

litterexpect-test 库 中借鉴了很多内容,该库也采用 MIT OR Apache-2.0 许可协议,并由 rust-analyzer 开发者(包括 Aleksey Kladov 和 Dylan MacKenzie)版权所有。

依赖项

~1–12MB
~106K SLoC