4 个版本 (2 个稳定版)

1.0.1 2022 年 9 月 5 日
0.1.0 2021 年 8 月 29 日
0.0.1 2021 年 8 月 22 日

#1031 in Rust 模式

GPL-3.0-or-later

95KB
2.5K SLoC

部分借用结构体。只要每个字段只能通过(最多)一个它们来可变访问,就可以实现多个同时的、可变的、部分借用。

主要参考文档和入口点

  • #[derive(PartialBorrow)] 为结构体推导部分借用。
  • [partial!()] 通过字段方便地指定部分借用的结构体类型。
  • use partial_borrow::prelude::* 在模块中使用此包。

动机

之前

# use std::ops::*;
# type GameState=(); type IPieces=(); type IOccults=();
# #[derive(Default)]
struct Instance { gs: GameState, ipieces: IPieces, ioccults: IOccults, /*.. ..*/ };

fn some_operation(gs: &mut GameState,   // Several pointers,
                  ipieces: &IPieces,    // all to fields of struct Instance.
                  ioccults: &IOccults,  // We only want to pass partial mut.
# _: RangeTo<RangeFull>) { }
#  let _ =
                  .. ..
# ;

# let mut instance = Instance::default();
some_operation(&mut instance.gs,    // Much typing to pass each field.
               &instance.ipieces,
               &instance.ioccults,
               .. ..
# );

之后

# use std::ops::*;
# use partial_borrow::prelude::*;
# type GameState=(); type IPieces=(); type IOccults=();
# #[derive(PartialBorrow,Default)]
# struct Instance { gs: GameState, ipieces: IPieces, ioccults: IOccults };
fn some_operation(// One pointer.  Need only list fields to be mut, here.
                  g: &mut partial!(Instance mut gs),
# _: RangeTo<RangeFull>) { }
# let _ =
                .. ..
# ;

# let mut instance = Instance::default();
some_operation(instance.as_mut(), .. .. // Type one parameter at the call site.
# );

示例

use partial_borrow::prelude::*;
use partial_borrow::SplitOff;

#[derive(Debug,PartialBorrow,Default)]
#[partial_borrow(Debug)]
pub struct Garden {
    trees: usize,
    gate_open: bool,
}

// This can't be an inherent method but it could be an extension
// trait method using a crate like `easy_ext`.
pub fn operate_gate(g: &mut partial!(Garden mut gate_open), open: bool) {
    *g.gate_open = open;
    eprintln!("operate_gate, {:?}", g);
}

#[derive(Debug)]
struct TreeAdmirer<'g> {
    g: &'g partial!(Garden const trees, !*),
}
impl TreeAdmirer<'_> {
    fn admire(&self) {
        eprintln!("I see {} trees {:?}", *self.g.trees, self);
        // XX eprintln!("gate open? {}", *self.g.gate_open);
        //    ^ error: type `F_gate_open<No, bool, Garden>` cannot be dereferenced
    }
}

let mut garden = Garden::default();
operate_gate(garden.as_mut(), true);
garden.trees += 1;
let (for_gate, rest) = SplitOff::split_off_mut(&mut garden);
let guest = TreeAdmirer { g: rest.as_ref() };
guest.admire();
operate_gate(for_gate, false);
guest.admire();

输出

operate_gate, Garden__Partial { trees: 0, gate_open: true }
I see 1 trees TreeAdmirer { g: Garden__Partial { trees: 1, gate_open: _ } }
operate_gate, Garden__Partial { trees: 1, gate_open: false }
I see 1 trees TreeAdmirer { g: Garden__Partial { trees: 1, gate_open: _ } }

easy_ext 的方法示例

# #[derive(Default,PartialBorrow)]
# pub struct Garden { gate_open: bool }
use partial_borrow::prelude::*;
use easy_ext::ext;

#[ext]
impl partial!(Garden mut gate_open) {
    pub fn operate_gate(&mut self, open: bool) { /*...*/ }
}

let mut garden = Garden::default();
garden.as_mut().operate_gate(true);

MIRI

该库目前依赖于整数/指针转换,"暴露"其指针,以便能够重新创建具有适当来源的引用。由于没有与 CHERI C/C++ 的 __attribute__((cheri_no_subobject_bounds))(CHERI PDF 中的第 16 页)对应的特性,或者以某种方式创建 ZST 引用而不缩小来源,因此需要进行整数转换。截至 2022 年 6 月 24 日的 Nightly 版本,Rust 严格的来源实验在 Rust API 中没有提供此类功能。

因此,运行 MIRI 需要设置 MIRIFLAGS+=' -Zmiri-permissive-provenance'

安全性

提供的API应该是安全可靠的。

兼容性 - 关于从引用中拥有的值的语言假设

partial-borrow 的安全性依赖于 Rust 不允许程序员通过仅有的引用 &mut T 获得一个拥有的值 T(假设某种类型 T 不允许这样做)。

然而,一些 Rust 库使用 unsafe 来扩展 Rust 语言以提供这种功能。例如,partial-borrowreplace_with 的组合是不安全的

保证

实现涉及大量的 proc-macro 生成的 unsafe 代码。有一些使用 miri 的测试,以及一个 正确性论点,形式为对自动生成的输出的详尽手写注释。尚未进行独立审查,也没有尝试使用形式化方法进行验证。

宏生成的代码大量引用此 crate 中的项目,其标准名称为 partial_borrow。理论上,使用该名称为其他事物是 不安全的,尽管如果该名称以某种方式引用了无关的 crate,proc-macro 输出几乎不可能编译,因此这似乎不是一个实际问题。

依赖项

~4–14MB
~176K SLoC