Skip to content

Infer fields using relative lifetimes #20

@burdges

Description

@burdges

We maybe have independent reasons for doing fields-in-traits, but they appear unnecessary for typical "partial borrowing" use cases.

We de facto loose lifetime elision with any partial borrowing scheme because you'll typically have methods that several fields but for different lifetimes and with different mutability. We should lean into this by expressing only disjointness using only "relative lifetimes". I think formally these resemble:

impl Trait for Type {
    type 'a<'self> where 'self: 'a<'self> = { field1, field2 };
}

In other words, relative lifetimes are associated lifetime constructors in the trait, or inherent type, which construct a lifetime that outlives 'self and represents only a subset of the fields of the underlying type.

We could infer the above complex syntax by simply (a) declaring disjointness for specific named relative lifetimes and (b) explicitly using these relative lifetimes in methods. All this resembles:

trait Trait {
    disjoint 'a, 'b, 'c;
    fn foo(&'a mut 'c self) -> Foo<'a>; 
    fn bar(&'b+'c mut self) -> &'b mut Bar; 
}

This says: At least three disjoint sets of fields 'a, 'b, 'c exist, all possibly empty. fn foo borrows 'c immutably and 'a mutably, but its return only borrows 'a mutably. fn bar borrows 'b and 'c mutably, but its return only borrows 'b mutably.

In other words, foo and bar could both be called sequentially since both abandon their overlapping 'c borrow, but neither could be called a second time until you abandon its return. Also, you cannot invoke foo and bar from simultaneous unrelated borrows. In particular, you cannot have separate conventional FnMuts built from foo and bar, unless you somehow make those FnMut traits be sub-traits of Trait.

Any impl Trait for Type causes rustc to infer which fields actually belong under 'a, 'b, 'c, based upon its actual implementations of foo and bar, but our above rules for invoking foo and bar have become part of the trait. It's possible for 'c to be inferred to empty, but we do not leak this emptiness so you still cannot invoke foo and bar from simultaneous unrelated borrows.

Advantages? We all love real fields but often you want them abstracted somehow, ala RefCell vs Mutex or Vec<T> vs [T; N]. A bunch of disjoint getter etc methods likely captures this better, ala fn get_fieldA(& 'fieldA self) -> impl Deref<Target=Whatever> + 'fieldA. It's already lower notation overhead, but you could lower this further by making disjoint method_name, ...; be sugar for disjoint 'method_name, ...; plus fn method_name(&'method_name [mut] self, ... It also works uniformly in traits and inherent types and likely plays nicer in semver.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions