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.
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:
In other words, relative lifetimes are associated lifetime constructors in the trait, or inherent type, which construct a lifetime that outlives
'selfand 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:
This says: At least three disjoint sets of fields
'a,'b,'cexist, all possibly empty.fn fooborrows'cimmutably and'amutably, but its return only borrows'amutably.fn barborrows'band'cmutably, but its return only borrows'bmutably.In other words,
fooandbarcould both be called sequentially since both abandon their overlapping'cborrow, but neither could be called a second time until you abandon its return. Also, you cannot invokefooandbarfrom simultaneous unrelated borrows. In particular, you cannot have separate conventionalFnMuts built fromfooandbar, unless you somehow make thoseFnMuttraits be sub-traits ofTrait.Any
impl Trait for Typecauses rustc to infer which fields actually belong under'a,'b,'c, based upon its actual implementations offooandbar, but our above rules for invokingfooandbarhave become part of the trait. It's possible for'cto be inferred to empty, but we do not leak this emptiness so you still cannot invokefooandbarfrom simultaneous unrelated borrows.Advantages? We all love real fields but often you want them abstracted somehow, ala
RefCellvsMutexorVec<T>vs[T; N]. A bunch of disjoint getter etc methods likely captures this better, alafn get_fieldA(& 'fieldA self) -> impl Deref<Target=Whatever> + 'fieldA. It's already lower notation overhead, but you could lower this further by makingdisjoint method_name, ...;be sugar fordisjoint 'method_name, ...;plusfn method_name(&'method_name [mut] self, ... It also works uniformly in traits and inherent types and likely plays nicer in semver.