Crazy thought: what if we had first-class place expressions in the language?
&(mut) currently enjoys a very special status. You can turn
&struct.field, which is a map
&'a Struct -> &'a Field, but it is impossible to express that map in the language. If we wanted to make it a function, what would it even act on? Similarly, it's impossible to express its basic constructor
t -> &t as a function
T -> &'a T. It doesn't act on a value, it acts on a place expression.
This built-in magic behaviour is desirable for other smart pointers, but currently cannot be ergonomically and safely expressed in the language.
What if there was some opaque compiler-provided place expression object, which could be passed around in user code? A place expression to an object of type
T could implement some trait
In its object form
dyn Place<T> it would basically be represented as a pointer
*mut T, with the distinction that we would know that it's in "dereferenced" form. In other words,
place: *const dyn Place<T> is just
*const T with the known type
T and no dyn metadata (similarly,
place: *mut dyn Place<T> is just
In the compile-time generic parameter form
fn foo<P: Place<T>>(place: P, ..) the place would carry compiler-provided metadata, which would allow a richer API. A place would know its provenance, so that we could check that e.g.
field_place is really a place to a field in
struct_place. Perhaps it should be possible to go from a field place to its parent struct place. This would allow to avoid dealing with explicit offsets of fields in structs in order to perform that operation.
A smart pointer type, e.g.
Ref<Struct>, would be able to provide the place of its contained data, i.e. a mapping
impl Place<Ref<Struct>> -> impl Place<Struct> (it would basically just return the dereferenced inner pointer, as a place). Given
s: Place<Struct>, we can get
s.f: Place<Field>, which can be passed into
Ref::restrict(self: impl Place<Ref<Struct>>, field: impl Place<Field>) -> impl Place<Ref<Field>>
The instance of
Ref can be recovered by converting place expression into a value expression, as usual. But if so desired, you could convert
&Ref<Field> with the same function and a referencing.
Since the compiler has full information about both places, it can check that
field is really a place in the inner struct and not just some stray pointer. This means that the restriction function can be entirely safe. With a pointer-based or offset-based definition of
restrict, I just don't see a way to make a safe API.
Composability becomes easy. To turn
Ref<MaybeUninit<Field>>, we first extract the place of the field via
Place<Ref<MaybeUninit<Struct>>> -> Place<MaybeUninit<Struct>> -> Place<Struct> -> Place<Field>
We get the required instance of
Ref<MaybeUninit<Field>> by spinning that process in reverse with
restrict functions, i.e.
MaybeUninit::restrict(impl Place<MaybeUninit<Struct>>, impl Place<Field>) -> impl Place<MaybeUninit<Field>> Ref::restrict(impl Place<Ref<MaybeUninit<Struct>>>, impl Place<MaybeUninit<Field>>) -> impl Place<Ref<MaybeUninit<Field>>>
The last line is valid since we track the place provenance, and so we know that the given
field: Place<MaybeUninit<Field>> is really a place within
struct: Place<MaybeUninit<Struct>>. Also note that you can't turn
impl Place<Struct> into e.g.
RefMut doesn't provide a corresponding constructor, and the
RefMut::restrict method requires already having a valid
RefMut to restrict.
First-class place expressions would also be able to solve many (most? all?) cases of proliferation of
iter/iter_mut methods, which really don't care about the mutability, and can be essentially generated by a macro. With first-class place expression, we would have a function
fn get_place(self: impl Place<[T]>, n: usize) -> impl Place<T>
which computes the resulting place directly operating on the places of the slice (currently all operations differ only in pervasive
const/mut dichotomy). Similarly, we would have
fn iter_place(self: impl Place<[T]>) -> impl Iterator<Item=impl Place<T>>
and the end-user would choose the desired value iterator by applying the corresponding smart pointer constructor. It could be
&mut T iterators as usual, but also
Option<&T> or even
&MaybeUninit<T>, if some unsafe trickery requires it.