1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
//! # 👓 Improving Readability and Maintainability with Partial Borrows
//!
//! Managing complex systems in Rust often involves dealing with deeply nested and interconnected
//! data. As these systems scale, maintaining clean, readable, and flexible code becomes
//! challenging, especially when borrowing different parts of a large structure.
//!
//! Traditional approaches, like passing entire structs or breaking them into multiple parameters,
//! quickly become cumbersome and error-prone. They clutter function signatures, reduce
//! flexibility, and can lead to borrowing conflicts that complicate code maintenance.
//!
//! **Partial borrows** offer an elegant solution by allowing functions to borrow only the fields
//! they need. This leads to:
//! - **Simpler, cleaner function signatures**
//! - **Easier code maintenance and extension**
//! - **Fewer borrow checker conflicts**
//!
//! In this guide, we'll explore how partial borrows can dramatically improve the readability and
//! maintainability of your code.
//!
//! # 😵💫 What problem does it solve?
//!
//! Consider a rendering engine requiring storage for geometries, materials, meshes, and scenes.
//! These entities often form a reference graph (e.g., two meshes can use the same material). To
//! handle this, you can either:
//!
//! - Use `Rc<RefCell<...>>`/`Arc<RefCell<...>>` for shared ownership, which risks runtime errors.
//! - Store the entities in registries and use their indices as references.
//!
//! We opt for the latter approach and create a root registry called `Ctx`:
//!
//! ```
//! // === Data ===
//! pub struct Geometry { /* ... */ }
//! pub struct Material { /* ... */ }
//! pub struct Mesh {
//! /// Index of the geometry in the `GeometryCtx` registry.
//! pub geometry: usize,
//! /// Index of the material in the `MaterialCtx` registry.
//! pub material: usize
//! }
//! pub struct Scene {
//! /// Indexes of meshes in the `MeshCtx` registry.
//! pub meshes: Vec<usize>
//! }
//!
//! // === Registries ===
//! pub struct GeometryCtx { pub data: Vec<Geometry> }
//! pub struct MaterialCtx { pub data: Vec<Material> }
//! pub struct MeshCtx { pub data: Vec<Mesh> }
//! pub struct SceneCtx { pub data: Vec<Scene> }
//!
//! // === Root Registry ===
//! pub struct Ctx {
//! pub geometry: GeometryCtx,
//! pub material: MaterialCtx,
//! pub mesh: MeshCtx,
//! pub scene: SceneCtx,
//! // Possibly many more fields...
//! }
//! ```
//!
//! Some functions require mutable access to only part of the root registry. Should they take a
//! mutable reference to the entire `Ctx` struct, or should each field be passed separately?
//! Passing the entire `Ctx` is inflexible and impractical. Consider the following code:
//!
//! ```compile_fail
//! fn render_pass1(ctx: &mut Ctx) {
//! for scene in &ctx.scene.data {
//! for mesh in &scene.meshes {
//! render_scene(ctx, *mesh)
//! }
//! }
//! render_pass2(ctx);
//! }
//!
//! fn render_pass2(ctx: &mut Ctx) {
//! // ...
//! }
//!
//! fn render_scene(ctx: &mut Ctx, mesh: usize) {
//! // ...
//! }
//! ```
//!
//! At first glance, this might seem reasonable, but it will be rejected by the compiler:
//!
//! ```bash
//! Cannot borrow `*ctx` as mutable because it is also borrowed as
//! immutable:
//!
//! | for scene in &ctx.scene.data {
//! | ---------------
//! | |
//! | immutable borrow occurs here
//! | immutable borrow later used here
//! | for mesh in &scene.meshes {
//! | render_scene(ctx, *mesh)
//! | ^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
//! ```
//!
//! Passing each field separately compiles, but becomes cumbersome and error-prone as the number of fields grows:
//!
//! ```
//! # // === Data ===
//! # pub struct Geometry { /* ... */ }
//! # pub struct Material { /* ... */ }
//! # pub struct Mesh {
//! # pub geometry: usize,
//! # pub material: usize
//! # }
//! # pub struct Scene {
//! # pub meshes: Vec<usize>
//! # }
//! #
//! # // === Registries ===
//! # pub struct GeometryCtx { pub data: Vec<Geometry> }
//! # pub struct MaterialCtx { pub data: Vec<Material> }
//! # pub struct MeshCtx { pub data: Vec<Mesh> }
//! # pub struct SceneCtx { pub data: Vec<Scene> }
//! #
//! # // === Root Registry ===
//! # pub struct Ctx {
//! # pub geometry: GeometryCtx,
//! # pub material: MaterialCtx,
//! # pub mesh: MeshCtx,
//! # pub scene: SceneCtx,
//! # // Possibly many more fields...
//! # }
//! #
//! fn render_pass1(
//! geometry: &mut GeometryCtx,
//! material: &mut MaterialCtx,
//! mesh: &mut MeshCtx,
//! scene: &mut SceneCtx,
//! // Possibly many more fields...
//! ) {
//! for scene in &scene.data {
//! for mesh_ix in &scene.meshes {
//! render_scene(
//! geometry,
//! material,
//! mesh,
//! // Possibly many more fields...
//! *mesh_ix
//! )
//! }
//! }
//! render_pass2(
//! geometry,
//! material,
//! mesh,
//! scene,
//! // Possibly many more fields...
//! );
//! }
//!
//! fn render_pass2(
//! geometry: &mut GeometryCtx,
//! material: &mut MaterialCtx,
//! mesh: &mut MeshCtx,
//! scene: &mut SceneCtx,
//! // Possibly many more fields...
//! ) {
//! // ...
//! }
//!
//! fn render_scene(
//! geometry: &mut GeometryCtx,
//! material: &mut MaterialCtx,
//! mesh: &MeshCtx,
//! // Possibly many more fields...
//! mesh_ix: usize
//! ) {
//! // ...
//! }
//! ```
//!
//! # 🤩 Partial borrows for the rescue!
//! This crate provides the `partial` macro, which we recommend importing under a shorter alias for concise syntax:
//!
//! ```
//! // === Data ===
//! # pub struct Geometry { /* ... */ }
//! # pub struct Material { /* ... */ }
//! # pub struct Mesh {
//! # pub geometry: usize,
//! # pub material: usize
//! # }
//! # pub struct Scene {
//! # pub meshes: Vec<usize>
//! # }
//!
//! # // === Registries ===
//! # pub struct GeometryCtx { pub data: Vec<Geometry> }
//! # pub struct MaterialCtx { pub data: Vec<Material> }
//! # pub struct MeshCtx { pub data: Vec<Mesh> }
//! # pub struct SceneCtx { pub data: Vec<Scene> }
//! #
//! use borrow::partial as p;
//! use borrow::traits::*;
//!
//! #[derive(borrow::Partial)]
//! #[module(crate)]
//! pub struct Ctx {
//! pub geometry: GeometryCtx,
//! pub material: MaterialCtx,
//! pub mesh: MeshCtx,
//! pub scene: SceneCtx,
//! }
//!
//! # pub fn main() {}
//! ```
//!
//! The macro allows you to parameterize borrows similarly to how you parameterize types. It
//! implements the syntax proposed in
//! [Rust Internals "Notes on partial borrow"](https://internals.rust-lang.org/t/notes-on-partial-borrows/20020),
//! extended with utilities for increased expressiveness. Please refer to this [crate documentation to learn about the syntax](crate#-borrowpartial-p-macro).
//! Let's apply these concepts to our rendering engine example:
//!
//! ```
//! # // === Data ===
//! # pub struct Geometry { /* ... */ }
//! # pub struct Material { /* ... */ }
//! # pub struct Mesh {
//! # pub geometry: usize,
//! # pub material: usize
//! # }
//! # pub struct Scene {
//! # pub meshes: Vec<usize>
//! # }
//! #
//! # // === Registries ===
//! # pub struct GeometryCtx { pub data: Vec<Geometry> }
//! # pub struct MaterialCtx { pub data: Vec<Material> }
//! # pub struct MeshCtx { pub data: Vec<Mesh> }
//! # pub struct SceneCtx { pub data: Vec<Scene> }
//! #
//! # use borrow::partial as p;
//! # use borrow::traits::*;
//! #
//! # #[derive(borrow::Partial)]
//! # #[module(crate)]
//! # pub struct Ctx {
//! # pub geometry: GeometryCtx,
//! # pub material: MaterialCtx,
//! # pub mesh: MeshCtx,
//! # pub scene: SceneCtx,
//! # }
//! #
//! fn render_pass1(mut ctx: p!(&<mut *> Ctx)) {
//! let (scene, mut ctx2) = ctx.borrow_scene();
//! for scene in &scene.data {
//! for mesh in &scene.meshes {
//! render_scene(p!(&mut ctx2), *mesh)
//! }
//! }
//! render_pass2(ctx);
//! }
//!
//! fn render_pass2(ctx: p!(&<mut *> Ctx)) {
//! // ...
//! }
//! fn render_scene(ctx: p!(&<mesh, mut geometry, mut material> Ctx), mesh: usize) {
//! // ...
//! }
//!
//! # pub fn main() {}
//! ```