-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Partial Types (v3) #3736
base: master
Are you sure you want to change the base?
RFC: Partial Types (v3) #3736
Conversation
``` | ||
Partiality: .{ PartialFields* } | ||
PartialFields: PermittedField (, PermittedField )* ,? | ||
PermittedField: IDENTIFIER | TUPLE_INDEX |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if you had:
struct Foo {
a: i32,
bar: Bar,
}
struct Bar {
b: f32,
c: String,
}
and you wanted to borrow both a
and bar.b
but not bar.c
?
I think extending partial references to allow this would be useful:
impl Foo {
fn baz(&self.{a, bar.b}) {
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I'll update this proposal to allow sub-partiality:
Minimal Partiality we could write:
Partiality: .{ PartialFields* }
PartialFields: PartialField (, PartialField )* ,?
PartialField: PermittedField
PermittedField: IDENTIFIER | TUPLE_INDEX
If we wish to describe partial structs with partial structs inside, we must have a bit more complex Partiality:
PartialField: PermittedField Partiality?
I don't believe this RFC sufficiently explains the interaction between partial types and subtyping. I believe introducing new subtyping for things that aren't lifetimes is quite hazardous to Rust due to problems with inference and existing interactions with |
Could you explain with more details? My vision: struct StructABC { a: u32, b: i64, c: f32, }
let s = StructABC {a: 4, b: 7, c: 0.0};
let sa = & s.a;
let sb = & s.b;
let sc = & s.c;
let rs = & s; With this proposal I suggest to allow also: let sa = & s.{a}; // same as let sa = &s (a: u32);
let sb = & s.{b}; // same as let sb = &s (b: i64);
let sc = & s.{c}; // same as let sc = &s (c: f32);
let sab = & s.{a,b}; // same as let sab = &s (a: u32, b: i64);
let sbc = & s.{b,c}; // same as let sbc = &s (b: i64, c: f32);
let sac = & s.{a,c}; // same as let sac = &s (a: u32, c: f32);
let rs = & s.{a,b,c}; // same as let rs = & s; |
As I'v observed before, rust could infer partial types using special lifetime annotations on traits methods: Inferred partial types have vastly less syntax than any other partial borrowing scheme. You never name fields explicitly, but instead name groups of fields using an implicitly higher order lifetime, for which rustc infers the fields. In essence, subtyping gets done using implicitly higher order lifetimes. |
an additional benefit of that scheme -- since it uses abstract field groups (lifetimes for that proposal), it works great even if you can't name fields due to visibility or the actual type being behind a generic (e.g. in traits) so you don't know what the field names are. Additionally, for visibility, I think we shouldn't be trying to punch holes through to allow naming private fields, since private field names are supposed to be an implementation detail and we should be allowed to change/add/delete private fields as we see fit without breaking downstream users. |
I think it would be good for this RFC to talk a bit about visibility. My assumption is that to be able to write But can a method |
Ok, I'll try to write more clearly in RFC about this.
Why? When you write |
I'm primarily concerned about the cross-crate use case: if there's a crate So a crate |
@TimNN Thank you for the idea to include a part "Unresolved Questions" of RFC as required part.
Ok, I get it and agree with that. fn baz(foo : & Foo) {
let fpubs = &foo.{pubfld1, pubfld2, pubfld3,};
let fprivs = &foo.{off pubfld1, pubfld2, pubfld3,};
// ....
} |
My original understanding was that, assuming the fields are private (the situation of course changes if they are public), the rule should be that partial borrows of a struct should only be able to be written by the implementor, and is only observable by the user when using functions from the implementor. Let's say we had a type
then the user would be able to
The point of having the partial borrow in the signature of the function is that it is something the maintainer has to uphold (with wrappers over raw types, they have to logically uphold it to maintain soundness, but with higher level things the borrow checker will always intervene if a maintainer tries to change the internals to use the wrong sets of borrows, unless they change the signatures but changing the signatures is a breaking change). So the user does not have to worry about breakage. But, I am seeing now that the user should also have access to the specific sets of borrows available through the public interface, so that when they are writing their own wrapper types and methods over
The syntax looks like it could get ugly real fast, there has to be some kind of typing or aliasing for sets of lifetimes like a more explicit version of Burdge's proposal. Also, is it possible to allow mixes of mutable and immutable or is that something that fundamentally doesn't make sense? I haven't thought too much about it and am just chiming in on the discussion. edit: suppose I added
without something like this partial borrows is useless in some circumstances |
The inferred partial types proposal mixes mutable and immutable using "algebra of relative lifetimes".
I suppose traits' relative lifetimes could be exposed via turbofish like
Or maybe something nicer than turbofish like
Anyways: Rust code depends heavily upon traits. A trait based approach like inferred partial types facilitates much bigger traits, like those arising in web frameworks, GUIs, DBs, and custom buisness logic. As a particular scenario, you've developed internal buisness logic mega-trait which makes borrowing awkward, so then you revisit how its methods should be used, and how its impls utilize state, add internal atomics or mutexs where required, and add relative lifetimes so that rustc permits the desired parallel borrowing. I doubt type & field based approaches really serve many users, especially if being more explicit makes them a syntactic nightmare. Inferred partial types could support enums far more effectively than explicit partial borrowing too, which again helps buisness logic enormously. |
Yes, this would be better.
Is it allowed to use nested partiality? Yes,
In this RFC - no, but in "Future possibilities" - yes, I add describe partial mutable fields.
No, syntax for partial mutability and partial borrows with mixed mutability looks like this: pub fn other_stuff(& mut.{example.{storage}} self.{example.{len, storage}}) -> &mut [u8] { /* .. */ } |
When you need to specify the fields at the call-site, isn't this super close to just passing the fields one by one (or grouping them in a new struct of references), which the current type system already allows? I feel like this approach doesn't give enough of a benefit. |
A strong motivation for this is disjointness analysis (particularly for mutable references) without needing to make all fields public. struct SomeStruct {
a: Foo,
b: Bar,
}
impl SomeStruct {
fn a_mut(&mut self.{a}) -> &mut Foo { &mut self.a }
fn b_mut(&mut self.{b}) -> &mut Bar { &mut self.b }
}
fn some_func(x: &mut SomeStruct) {
let a = x.a_mut();
let b = x.b_mut();
do_something(a, b);
} Right now, this is impossible without making |
This RFC proposes Partial Types as universal and type safe solution to "partial borrowing" like problems.
This RFC is a third try and it is based on Partial Types (v2) #3426
Rendered
Example: