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:
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'd likely want some mechanism by which you could promise that a supertrait could not conflict too, but I'm unsure if the same relative lifetime "algebra" siffices there.
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
'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:
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
andbar
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 invokefoo
andbar
from simultaneous unrelated borrows. In particular, you cannot have separate conventionalFnMut
s built fromfoo
andbar
, unless you somehow make thoseFnMut
traits be sub-traits ofTrait
.Any
impl Trait for Type
causes rustc to infer which fields actually belong under'a
,'b
,'c
, based upon its actual implementations offoo
andbar
, but our above rules for invokingfoo
andbar
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 invokefoo
andbar
from simultaneous unrelated borrows.Advantages? We all love real fields but often you want them abstracted somehow, ala
RefCell
vsMutex
orVec<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.