Partially borrow a struct. Multiple simultaenous mutable partial borrows are possible, so long as each field is only mutably accessible via (at most) one of them. # Primary reference documentation and entrypoints * [`#[derive(PartialBorrow)]`](macro@PartialBorrow) Derive partial borrowing for a struct. * [`partial!()`] Conveniently specify a partially borrowed struct type, by its field(s). * `use partial_borrow::`[`prelude`]`::*` for convenient use of this crate in your module. # Motivation Before ``` # 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) { } # let _ = .. .. # ; # let mut instance = Instance::default(); some_operation(&mut instance.gs, // Much typing to pass each field. &instance.ipieces, &instance.ioccults, .. .. # ); ``` After ``` # 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) { } # let _ = .. .. # ; # let mut instance = Instance::default(); some_operation(instance.as_mut(), .. .. // Type one parameter at the call site. # ); ``` # Example ``` 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` 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(); ``` Output ```text 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: _ } } ``` # Method example with 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 The library currently relies on integer/pointer conversions, "exposing" its pointers, so as to be able to recreate references with appropriate provenance. The integer conversions are necessary in lie of a feature corresponding to [CHERI C/C++](https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-947.pdf#page=16)'s `__attribute__((cheri_no_subobject_bounds))` (p16 in the CHERI PDF), or some other way to make a ZST reference without narrowing the provenance. As of Nightly 2022-06-24, the Rust Strict Provenance experiment does not provide such a feature at the Rust API, Therefore, running MIRI requires setting `MIRIFLAGS+=' -Zmiri-permissive-provenance'` # Safety The provided API is supposed to be safe and sound. ## Compatibility - language assumption about owned values from references `partial-borrow` relies for soundness on the fact that Rust does not permit the programmer to obtain an owned value `T`, if they are given only reference `&mut T`. (Assuming some type `T` which doesn't specifically allow this.) However, there are some Rust libraries which use `unsafe` to extend the Rust language to provide this facility. For example, **the combination of `partial-borrow` with `replace_with` is unsound**. ## Assurance The implementation involves an awful lot of proc-macro-generated `unsafe`. There are tests with miri and a [correctness argument](https://salsa.debian.org/iwj/rust-partial-borrow/-/blob/main/argument/argument.rs) in the form of extensive hand-written annotations to an autogenerated output. There has not been any independent review, and no attempt at validation using formal methods. The macro-generated code refers extensively to items from this crate, under its canonical name `partial_borrow`. Using that name for something else is in theory *unsound*, although it is highly unlikely that the proc-macro output would compile if that name somehow referred to an unrelated crate, so this does not seem to be a practical concern.