This is a continuation of a previous discussion and many concepts/syntax are taken from there.
I was trying to solve the problem of view types in Rust (rather, lack thereof) by binding a lifespan to each field and having the programmer specify the lifespans in the function signature such that the ownership rules allow different composite lifespans of the struct to coexist while preserving the ownership rules. It looks something along the lines of
pub struct Struct {
pub x: i32,
pub y: i32,
origin: (i32, i32)
}
struct ref &(~x, ~y, ~rest) Struct {
x: ~x,
y: ~y,
origin: ~rest
}
fn get_x_mut(view: &('_ mut, '!, '!) Struct) -> &'_ mut i32 {...}
Where ~x
denotes the lifespan named x
.
This syntax is cumbersome because each lifespan has to be specified. The next iteration looked like this:
...
struct ref &{x: ~x, y: ~y, rest: ~rest} Struct {
x: ~x,
y: ~y,
origin: ~rest
}
fn get_x_mut(view: &{mut x, ..} Struct) -> &mut i32 {...}
Which looks much better at the cost of introducing some uncanny declaration syntax. struct ref &{x: ~x, y: ~y, rest: ~rest} Struct
is clearly some spawn of the devil!
Lifespans - Redefinition
Previously, lifespans were defined as lifetime-access pairs, like "x = 'x mut
or "y = '!
. Composite lifespans are collections of such entities.
This does not only introduce some new (nasty) syntax - "x
, it also allows one to be generic over mutability by using a lifespan instead of a lifetime. The issues it brings can be alleviated by prohibiting the use of arguments which use a lifespan in their definition, but I propose we forget about the difference between lifespans and composite lifespans and define a lifespan as either a lifetime ('a
), a mut lifetime ('a mut
) or a collection of lifespans. Though this definition is recursive, it also contains the terminating conditions, so we're good.
References with different lifespans can exist at the same time as long as references of the individual components can, along the component index (i.e. a set of { (~a_i, ~b_i) }
may exist if and only if {a_i}
and {b_i}
do).
Ref Traits
I was thinking of a way to improve the syntax without sacrificing flexibility the ability to name the lifespans and had an epiphany: "Lifetimes", I thought, "are very similar to types - they both are statically solvable, both can appear in generic contexts and are both required for fully specifying a reference. Why not, then, make them truly on equal ground?". This thought lead me to the idea of ref traits.
Like regular traits, ref traits have items - namely, associated lifespans. They also may be generic and appear in constraints. Consider the following example:
pub struct Struct {
pub x: i32,
pub y: i32,
origin: (i32, i32)
}
// Define a ref trait for lifespans of references of 'coordinate-like' types
pub ref trait Coord {
lifespan ~x = '!; // Associated lifespan, defaulted to the nil lifetime'
lifespan ~y = '!; //'
lifespan ~rest = '!; //'
}
// Bind the ref trait `Coord` to `&Struct`.
// Now, any reference to a `Struct` shall have a lifespan that could be decomposed into `~x`, `~y` and `~rest`
struct ref &~a Struct
where
~a: Coord
{
x: ~a::~x,
y: ~a::~y,
origin: ~a::~rest
}
fn get_x_mut<~a>(view: &~a Struct) -> &mut i32
where
~a: Coord<~x = '_ mut> // similar to `Coord<'_ mut, '!, '!>` and `Coord<~x = '_ mut, ~y = '!, ~rest = '!>`
{...}
fn get_y_mut(view: &(impl Coord<~y = '_ mut>) Struct) -> &mut i32 {...}
/*
By default, a primitive lifespan (`'a` or `'a mut`) implements any ref trait by substituting itself in every associated lifespan.
It is desirable to have the capability to override this default behavior
*/
impl<'a> Coord for 'a {
// lifespan ~x = '!;
// lifespan ~y = '!;
lifespan ~rest = 'a;
}
fn get_origin(view: &Struct) -> &(i32, i32) {...} // silently replace `&Struct` by `&(impl Coord<~rest = '_>) Struct`
// You can also define custom lifespans that would work with `Struct`
impl<~a, ~b> Coord for (~a, ~b) {
lifespan ~x = ~a;
lifespan ~y = ~b;
// lifespan ~rest = '!;
}
fn get_x_y_mut(view: &('_ mut, '_ mut) Struct) -> (&mut i32, &mut i32) {...}
This syntax is much more verbose than all the previous suggestions, but it is more 'Rusty', allowing one to unleash the full potential of generics, constraints and traits.