Skip to main content

_01_places/
lib.rs

1//! Crate to experiment with the API proposed in
2//! <https://nadrieril.github.io/blog/2025/11/11/truly-first-class-custom-smart-pointers.html>.
3#![feature(ptr_metadata)]
4
5use std::ptr::NonNull;
6
7mod basic_impls;
8mod projection;
9pub use projection::*;
10mod place_ops;
11pub use place_ops::*;
12
13/// Make a unit struct that represents the projection to a particular struct
14/// field. Only works for sized types.
15///
16/// Syntax: `mk_field_proj!(struct FooAProj(Foo.a: A))`.
17#[macro_export]
18macro_rules! mk_field_proj {
19    (struct $name:ident($src_ty:ident.$field:ident: $tgt_ty:ty)) => {
20        #[derive(Clone)]
21        struct $name;
22        impl Projection for $name {
23            type Source = $src_ty;
24            type Target = $tgt_ty;
25            fn offset(
26                &self,
27                _: <Self::Source as core::ptr::Pointee>::Metadata,
28            ) -> usize {
29                core::mem::offset_of!($src_ty, $field)
30            }
31            fn project_metadata(
32                &self,
33                _: <Self::Source as core::ptr::Pointee>::Metadata,
34            ) -> <Self::Target as core::ptr::Pointee>::Metadata {
35            }
36        }
37    };
38}
39
40/// Macro that simulates the proposed new syntax. Derefs must be explicit and
41/// the identifiers used for field projections must actually be values of some
42/// `Projection` type, e.g. built with `mk_field_proj`.
43///
44/// Examples:
45/// ```text
46/// (*p).a
47/// -> a.read(&raw const p)
48/// (*p).a.b
49/// -> a.compose(b).read(&raw const p)
50/// (**p).a
51/// -> a.read(NoopProj::default().deref(&raw const p))
52/// (*(*p).ptr_a).b
53/// -> b.read(ptr_a.deref(&raw const p))
54/// (*p).a = foo()
55/// -> a.write(&raw const p, foo())
56/// @R *p
57/// -> NoopProj::default().borrow::<_, R<_>>(&raw const p)
58/// @R (*p).a
59/// -> a.borrow::<_, R<_>>(&raw const p)
60/// @R (**p).a
61/// -> a.borrow::<_, R<_>>(NoopProj::default().deref(&raw const p))
62/// ```
63#[macro_export]
64macro_rules! p {
65    // Parse the input syntax. Step one was to check if we're borrowing or not,
66    // which happened at the user-facing entrypoint.
67    // Step 2: identify the base place, which is either a local or a deref.
68    (#parse_base(
69        $action:ident($($action_args:tt)*),
70        input(
71            // A deref of a potentially-complex place expression.
72            (*$($place:tt)*)
73            $($rest:tt)*
74        )
75    )) => {{
76        use $crate::ProjectionExt;
77        let start = p!(#build_start(deref($($place)*)));
78        $crate::p!(#parse_proj(
79            $action($($action_args)*),
80            ptr(start),
81            project(),
82            input($($rest)*)
83        ))
84    }};
85    (#parse_base(
86        $action:ident($($action_args:tt)*),
87        input(
88            // A deref of a local, with no projections.
89            *$local:ident
90        )
91    )) => {
92        $crate::p!(#parse_base(
93            $action($($action_args)*),
94            input($local.*)
95        ))
96    };
97    (#parse_base(
98        $action:ident($($action_args:tt)*),
99        input(
100            // A postfix deref
101            $local:ident.*
102            $($rest:tt)*
103        )
104    )) => {
105        $crate::p!(#parse_proj(
106            $action($($action_args)*),
107            ptr(&raw const $local),
108            project(),
109            input($($rest)*)
110        ))
111    };
112    (#parse_base(
113        $action:ident($($action_args:tt)*),
114        input(
115            // Not a deref so we remove the parens (to support `(x.a).b`).
116            ($($place:tt)*)
117            $($rest:tt)*
118        )
119    )) => {
120        $crate::p!(#parse_base(
121            $action($($action_args)*),
122            input($($place)* $($rest)*)
123        ))
124    };
125    // (#parse_base(
126    //     $action:ident($($action_args:tt)*),
127    //     input(
128    //         // A local
129    //         $local:ident
130    //         $($rest:tt)*
131    //     )
132    // )) => {
133    //     $crate::p!(#parse_proj(
134    //         $action($($action_args)*),
135    //         local($local),
136    //         project(),
137    //         input($($rest)*)
138    //     ))
139    // };
140    // Step 3: gather the possible projections.
141    (#parse_proj(
142        $action:ident($($action_args:tt)*),
143        $start:ident($($start_args:tt)*),
144        project($($fields:tt)*),
145        input(
146            . * // postfix deref
147            $($input:tt)*
148        )
149    )) => {{
150        // TODO
151        let p = $crate::p!(#parse_proj(
152            deref(),
153            $start($($start_args)*),
154            project($($fields)*),
155            input()
156        ));
157        $crate::p!(#parse_proj(
158            $action($($action_args)*),
159            ptr(p),
160            project(),
161            input($($input)*)
162        ))
163    }};
164    (#parse_proj(
165        $action:ident($($action_args:tt)*),
166        $start:ident($($start_args:tt)*),
167        project($($fields:tt)*),
168        input(
169            .$field:ident
170            $($rest:tt)*
171        )
172    )) => {
173        $crate::p!(#parse_proj(
174            $action($($action_args)*),
175            $start($($start_args)*),
176            project($($fields)*.$field),
177            input($($rest)*)
178        ))
179    };
180    (#parse_proj(
181        $action:ident($($action_args:tt)*),
182        $start:ident($($start_args:tt)*),
183        project($($fields:tt)*),
184        input(
185            $(= $rvalue:expr)?
186        )
187    )) => {
188        $crate::p!(#parse_assign(
189            $action($($action_args)*),
190            $start($($start_args)*),
191            project($($fields)*),
192            input($(= $rvalue)?)
193        ))
194    };
195    // Step 4: Detect an assignment, if any.
196    (#parse_assign(
197        read_or_write(),
198        $start:ident($($start_args:tt)*),
199        project($($proj_args:tt)*),
200        input(
201            = $rvalue:expr
202        )
203    )) => {
204        $crate::p!(#build(
205            write($rvalue),
206            $start($($start_args)*),
207            project($($proj_args)*),
208        ))
209    };
210    (#parse_assign(
211        read_or_write(),
212        $start:ident($($start_args:tt)*),
213        project($($proj_args:tt)*),
214        input()
215    )) => {
216        $crate::p!(#build(
217            read(),
218            $start($($start_args)*),
219            project($($proj_args)*),
220        ))
221    };
222    (#parse_assign(
223        $action:ident($($action_args:tt)*),
224        $start:ident($($start_args:tt)*),
225        project($($proj_args:tt)*),
226        input()
227    )) => {
228        $crate::p!(#build(
229            $action($($action_args)*),
230            $start($($start_args)*),
231            project($($proj_args)*),
232        ))
233    };
234
235    // Helpers for the final build.
236    // Compose some field projections.
237    (#compose_projs()) => { $crate::NoopProj::default() };
238    (#compose_projs(.$field:ident $($rest:tt)*)) => {
239        $field.compose(p!(#compose_projs($($rest)*)))
240    };
241    // Build the pointer expression we start with. For non-idents, we call back
242    // to our parsing logic to deref a complex place expression.
243    (#build_start(deref($ptr:ident))) => { &raw const $ptr };
244    (#build_start(deref($($place:tt)*))) => {
245        $crate::p!(#parse_base(deref(), input($($place)*)))
246    };
247
248    // Evaluate the intermediate values.
249    (#build(
250        $action:ident($($action_args:tt)*),
251        ptr($ptr:expr),
252        project($($proj_args:tt)*),
253    )) => {{
254        use $crate::ProjectionExt;
255        let proj = p!(#compose_projs($($proj_args)*));
256        $crate::p!(#do_action(
257            $action($($action_args)*),
258            base($ptr),
259            project(proj),
260        ))
261    }};
262
263    // Now we build the final expression.
264    (#do_action(
265        read(),
266        base($ptr:expr),
267        project($proj:expr),
268    )) => {
269        $proj.read($ptr)
270    };
271    (#do_action(
272        deref(),
273        base($ptr:expr),
274        project($proj:expr),
275    )) => {
276        $proj.deref($ptr.cast_mut())
277    };
278    (#do_action(
279        write($rvalue:expr),
280        base($ptr:expr),
281        project($proj:expr),
282    )) => {
283        $proj.write($ptr.cast_mut(), $rvalue)
284    };
285    (#do_action(
286        borrow($($ptr_ty:tt)*),
287        base($ptr:expr),
288        project($proj:expr),
289    )) => {
290        $proj.borrow::<_, $($ptr_ty)*>($ptr)
291    };
292
293    // Catch internal errors instead of looping back to the catch-all case
294    // below.
295    (#$($rest:tt)*) => {
296        compile_error!("Unsupported expression")
297    };
298
299    // Entrypoints.
300    // @_ place_expr (let inference determine the target pointer)
301    (@_ $($place:tt)*) => {
302        $crate::p!(#parse_base(borrow(_), input($($place)*)))
303    };
304    // @Ptr<ty_params> place_expr
305    (@$ptr:ident<$($ty:ty),*> $($place:tt)*) => {
306        $crate::p!(#parse_base(borrow($ptr<$($ty),*>), input($($place)*)))
307    };
308    // @Ptr place_expr
309    (@$ptr:ident $($place:tt)*) => {
310        $crate::p!(#parse_base(borrow($ptr<_>), input($($place)*)))
311    };
312    // Anything else
313    ($($place:tt)*) => {
314        $crate::p!(#parse_base(read_or_write(), input($($place)*)))
315    };
316}