- Feature Name:
partial_types
- Start Date: 2023-05-15
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
This proposal is universal flexible tool to work safe with partial Enums, Structs and Tuples in parameters, arguments, references and borrows.
Advantages: maximum type safety, maximum type control guarantee, no ambiguities, zero-cost-binary, flexibility, usability and universality.
Partial Types proposal is a generalization on "partial borrowing"-like proposals. Safe, Flexible controllable partial parameters for functions and partial consumption (including partial borrowing) are highly needed.
Partial Types extension gives to Sum Types (ST = T1 or T2 or T3 or ..
), Enums first of all, a good tool for "partial functions".
enum EnumABC { A(u32), B(i64), C(f32), }
// function with partial parameter Enum
fn print_A(a: EnumABC.{A}) {
println!("a is {}", a.0);
}
let ea = EnumABC::A(7);
// ea : EnumABC.{A} inferred
print_A(ea);
Partial Types extension gives to Product Types (PT = T1 and T2 and T3 and ..
), Structs and Tuples first of all, a good mathematical guarantee to borrow-checker that borrowing the whole variable with partial type and pretending to borrow just permitted fields is fully safe.
struct StructABC { a: u32, b: i64, c: f32, }
// function with partial parameter Struct
fn ref_a (s : & StructABC.{a}) -> &u32 {
&s.a
}
let s = StructABC {a: 4, b: 7, c: 0.0};
// partial expression at argument
let sa = ref_a(& s.{a});
And since it is a guarantee by type, not by values, it has zero cost in binary! Any type error is a compiler error, so no errors in the runtime.
This extension is not only fully backward-compatible, but is fully forward-compatible! Forward-compatibility is an ability to use updated functions old way.
Partial (Sub-)Types are types, which has not full range of possible values and they have limited access to own fields.
Partial Types are not Contract Types (Types with invariant), but this proposal could coexist with Contract Types.
Partiality of type (or partial type access) is written as Path.{fld1, fld2, ..}
after Path (Type name), where fld1
, fld2
, .. are only permitted fields of this type, the rest of fields has denied access.
It is forbidden to use somehow denied fields (like have outside access to private field), including access to read such field, to write, to borrow, to move. It is a compile error if someone try to access it.
(A) independent sub-proposal, if it implemented after (B), no extra syntax is needed.
For Sum Types (ST = T1 or T2 or T3 or ..
), for Enums partiality to type addition is enough to have full flexibility of using those sub-types.
enum MyEnum {
A(u32),
B { x: u32 },
C(u32),
D(u32),
}
fn print_A(a: MyEnum.{A}) {
println!("a is {}", a.0);
}
fn print_B(b: MyEnum.{B}) {
println!("b is {}", b.x);
}
fn print_no_pattern(e: MyEnum) {
match e {
MyEnum::A(_) => print_A(e), // e : MyEnum.{A} inferred
b @ MyEnum::B(..) => print_B(b), // b : MyEnum.{B} inferred
_ => (), // e : MyEnum.{C, D}; inferred
}
}
Type checker must guess (without calculating) from assigning values, binding values and matching values which is partial sub-type of Enum type. The more clever type-checker is, then more conclusions of Enum sub-type it has.
Sum-Typed argument type must match with function parameter type or argument type could has less permitted partiality then parameter type.
// Enum ~ Sum Type
enum E4 {A (i32), B(i32), C (i32), D(i32)}
fn do_eab(e : E4.{A, B}) { /* .. */ }
let e : E4;
do_eab(e); // e.{*} - error
let e : E4.{A, B, C};
do_eab(e); // e.{A, B, C} - error
let e : E4.{A, B};
do_eab(e); // e.{A, B} - Ok
let e : E4.{A} = E4::A(5);
do_eab(e); // e.{A} - Ok
let e : E4.{B} = E4::B(7);
do_eab(e); // e.{B} - Ok
Sum-Typed argument type must select for Implementations same type or type, which has more permitted partiality.
Implementation of sub-type must have same result as Implementation for full type.
So for ergonomic it is Ok to have for each "one-field sub-Enum" Implementation and one Implementation for "full Enum", which reuse one-field sub-Enum Implementation.
(B) independent sub-proposal
For Product Types PT = T1 and T2 and T3 and ..
), for structs, tuples we need not only partiality of a type, but also "partial access" expression: Expr .{fld1, fld2, ..}
, where fld1
, fld2
, .. are permitted fields of this type, the rest of fields are denied.
One step to partial borrows Structs and Tuples.
struct Point {
x: f64,
y: f64,
was_x: f64,
was_y: f64,
state : f64,
}
let mut p1 = Point {x:1.0, y:2.0, was_x: 4.0, was_y: 5.0, state: 12.0};
// p1 : Point
let ref_p1was = &mut p1.{wax_x, was_y};
// ref_p1was : &mut Point.{was_x, was_y}
let ref_p1now = &mut p1.{x, y};
// ref_p1now : &mut Point.{x, y}
It is simple and will be possible.
If explicit partiality is omitted, then implicit partiality is used.
For Types, implicit partiality is .{*}
(all fields are permitted).
For Expressions, implicit partiality is .{_}
("don't care" partiality).
We could alternatively permit to implicitly infer partiality for arguments. In this case we must explicitly write .{_}
or .{*}
to prevent inferring.
It is easy to write functions, which consume partial parameters:
impl Point {
fn ref_x (self : & Self.{x}) -> &f64 {
&self.x
}
fn refmut_y (self : &mut Self.{y}) -> &mut f64 {
&mut self.y
}
}
let ref_p1x = p1.ref_x();
let refmut_p1y = p1.refmut_y();
It is expected, that self
is always cut partiality of argument by same partiality as self-parameter by partial expression before use (even if implicit rules are off)!
Pseudo-rust:
fn ref_xy (self : & Self.{'a @( x, y)}) -> &f64 {
/* */
}
p1.ref_xy();
// which "desugar"
Point::ref_xy(& p1.{'a});
// which "desugar"
Point::ref_xy(& p1.{x, y});
Product-Typed argument type must match with function parameter type or argument type could has more permitted partiality then parameter type.
// Struct ~ Product Type
struct S4 {a : i32, b : i32, c : i32, d : i32}
fn do_sab(s : S4.{a, b}) { /* .. */ }
let s = S4 {a: 6, b: 7, c: 8, d: 9};
do_sab(s.{*}); // s.{*} - Ok
do_sab(s.{a, b, c}); // s.{a, b, c} - Ok
do_sab(s); // s.{a, b}, implicit partiality - Ok
do_sab(s.{a, b}); // s.{a, b} - Ok
do_sab(s.{a}); // s.{a} - error
do_sab(s.{b}); // s.{b} - error
Implementation of sub-Product-type is no needed.
(C?) maybe insecure sub-proposal, which could be added together or after (B), especially before (D) or alternative to (D)
Before (or instead of) adding (D) Partial Mutability extension it would be nice, if a general parameter Smv
as "same variable" is added in Implementations.
As an alternative keywords self1
, self2
are added.
The idea is that general parameter Smv
add same variable as 2nd parameter:
impl SomeStruct<Smv = Self>{
pub fn foo(self : &mut Self.{/*'a*/}, smv : & Smv.{/*'b*/})
}
var.foo();
// desugars
SomeStruct::foo(&mut var.{/*'a*/}, & var.{/*'b*/});
It is expected, that Smv
parameter is always cut partiality of argument by same partiality as self-parameter by partial expression before use (even if implicit rules are off)!
This makes partial borrowing fully flexible!
impl Point<Smv = Self>{
pub fn mx_rstate(self : &mut Self.{x}, smv : & Smv.{state})
{ *self.x += *smv.state; }
pub fn my_rstate(self : &mut Self.{y}, smv : & Smv.{state})
{ *self.y += *smv.state; }
pub fn mxy_rstate(self : &mut.{x,y} Self.{x, y, state}) {
/* ... */
Self::mx_rstate(self.{x}, smv); // explicit
Self::mx_rstate(self, smv); // same implicit
/* ... */
Self::mx_rstate(self.{y}, smv); // explicit
Self::mx_rstate(self, smv); // same implicit
/* ... */
}
}
This sub-proposal, has unresolved question is it secure not to check the origin if variable is the same if we explicitly write associated function
impl Bar<Smv = Self>{
fn foo(self : &mut Self::{x}, smv: & Smv::{y}) { /* */ }
}
Bar::foo(&mut bar.{x}, & bar.{y}); // Ok
Bar::foo(&mut bar.{x}, & baz.{y}); // Error? Ok?
I think it is insecure, error, but who knows.
If it is secure, then this sub-proposal is good.
(D) partly independent sub-proposal. If it is implemented before (B), then partly-mutable references are off.
For full flexibility of using partial borrowing partial mutability is needed (if (C) is not secure)!
For Product Partial Types (structs, tuples) we use "partial mutability" expression: mut .{fld1, fld2, ..}
, where fld1
, fld2
, .. are mutable fields of this type, the rest of fields are immutable(constant).
Partly mutable variables become possible for Product Partial Types:
struct S4 {a : i32, b : i32, c : i32, d : i32}
let mut.{a} s_ma = S4 {a: 6, b: 7, c: 8, d: 9};
let mut.{b, c} s_mbc = S4 {a: 6, b: 7, c: 8, d: 9};
let mut.{a, c, d} s_macd = S4 {a: 6, b: 7, c: 8, d: 9};
It is also possible to make partial-mutable references, if it is implemented after (B):
fn mab_s(s : &mut.{a,b} S4)
{ /* ... */ }
mab_s(&mut.{a,b} s_macd);
It is expected, that &mut.{..}
is a third type of borrowing!
If this extension is added, no extension (C) Several Selfs is needed (but it is no contradiction to use both extensions):
impl Point {
pub fn mx_rstate(self : &mut.{x} Self.{x, state})
{ /* ... */ }
pub fn my_rstate(self : &mut.{y} Self.{y, state})
{ /* ... */ }
pub fn mxy_rstate(self : &mut.{x,y} Self.{x, y, state}) {
/* ... */
self.{x, state}.mx_rstate(); // explicit
self.mx_rstate(); // same implicit
/* ... */
self.{y, state}.my_rstate(); // explicit
self.my_rstate(); // same implicit
/* ... */
}
}
(E) sub-proposal, which could be added together or after (B), it is better before (F)
This extension is not a mandatory. Tuple type has "naked" structure, so it would be handy have more pretty visuals, instead of mark all permitted fields in "partiality", write deny
before denied field.
let t :: (i32, &u64, f64, u8).{1,3};
// same as
let t :: (deny i32, &u64, deny f64, u8);
This extension is not just pretty, but useful with extension (F) partial initializing Tuples.
(F) sub-proposal, which could be added together or after (B), it is better after (E)
All syntax and semantic is ready to have implicit partial initializing and partial pattern deconstruction Structs. If not all fields are initialized, then variable has partial type. ЬBut maybe implicit rules are not the best idea.
Alternative to implicit partial initialization is explicit partial initialization,
struct S4 {a : i32, b : i32, c : i32, d : i32}
let s_abcd : S4 = S4 {a: 6, b: 7, c: 8, d: 9};
// Explicit partial initialization
let s_bd : S4.{b, d} = S4 {b: 7, d: 9, .. deny};
let s_a : S4.{a} = S4 {a: 6, .. deny};
// Implicit partial initialization (alternative)
let s_a : S4.{b} = S4 {b: 26};
// Explicit partial initialization
let s_bc = S4 {b: 7, c: 8, .. deny}; // s_bc : S4.{b, c}
let s_c = S4 {c: 8, .. deny}; // s_c : S4.{c}
// Implicit partial initialization (alternative)
let s_d = S4 {d: 9}; // s_d : S4.{d}
let s_abc : S4.{a, b, c} = S4 {a: 6, b: 7, c: 8, deny d};
let s_abd = S4 {a: 6, b: 7, deny c, d: 9}; // s_abd : S4.{a, b, d}
Sure, it is forbidden to fill private fields outside of module.
If extension (E) Explicit Deny Fields is added, then partial initializing Tuples is also possible with deny
pre-field and maybe miss Expr if type is clear.
Or partial expression is used:
let t_0123: (i32, u16, f64, f32) = (6i32, 7u16, 8.0f64, 9.0f32);
let t_013 : (i32, u16, f64, f32).{0, 1, 3} = (6i32, 7u16, deny 8.0f64, 9.0f32);
let t_0 : (i32, u16, f64, f32).{0} = (6i32, deny, deny, deny);
let t_012 : (i32, u16, f64, deny f32) = (6i32, 7u16, 8.0f64, deny 9.0f32);
let t_23 : (deny i32, deny u16, f64, f32) = (deny, deny 7u16, 8.0f64, 9.0f32);
let t_3 : (deny i32, deny u16, deny f64, f32) = (deny, deny, deny, 9.0f32);
let t_01 : (i32, u16, f64, f32).{0, 1} = (6i32, 7u16, 8.0f64, 9.0f32).{0, 1};
let t_02 = (6i32, deny 7u16, 8.0f64, deny 9.0f32); // t_02 : (i32, u16, f64, f32).{0, 2}
let t_12 = (6i32, 7u16, 8.0f64, 9.0f32).{1, 2}; // t_12 : (i32, u16, f64, f32).{1, 2}
If extension (E) Explicit Deny Fields is added, then alternative partial initializing Structs is also possible with deny
pre-field and maybe miss Expr if type is clear.
struct S4 {a : i32, b : i32, c : i32, d : i32}
let s_abc : S4.{a, b, c} = S4 {a: 6, b: 7, c: 8, deny d};
let s_abd = S4 {a: 6, b: 7, deny c, d: 9}; // s_abd : S4.{a, b, d}
(G) sub-proposal, which could be added together or after (B)
It is also become possible to use several variables which have partial struct-type and their permitted fields must not overlap, for updating omitted fields:
struct S4 {a : i32, b : i32, c : i32, d : i32}
let s = S4 {a: 8, b: 9, c: 11};
let rs_c = & s.{c};
// if (F)
let s_ab : S4.{a, b} = S4 {a: 6, b: 7, .. deny};
// if (F)
let s_abc = S4{b: 11, ..s_ab, .. *rs_c, .. deny}; // s_abc : S4.{a, b, c}
let s1 : S4 = S4{b: 11, d: 17, ..s_ab, .. *rs_c};
let s2 : S4 = S4{d: 17, ..s_ab, .. *rs_c};
(H) sub-proposal, which could be added together or after (B) and/or (A)
Inferred Structs and Enums is not a mandatory extension, and it is alternative to "anonymous" Srtucts and Enums.
struct StructInfr {..}
let s_c = StructInfr {c: 8i32}; // s_c : StructInfr/*{c: i32, .. }*/.{c}
let s_d = StructInfr {d: 8u8}; // s_d : StructInfr/*{c: i32, d: u8, .. }*/.{d}
enum EnumInfr {..}
let e_c = EnumInfr::C(8i32); // e_c : EnumInfr/*{C(i32), ..}*/.{C}
let e_d = EnumInfr::D{d: 77u64}; // es_d : EnumInfr/*{C(i32), D{d: u64}, ..}*/.{D}
It is expected, that type-checker could infers type from using its fields.
(IJ) partly independent sub-proposal sub-proposal, which could be added together or after (B)
Rust allow to write uninitialized variables. But they are fully uninitialized. This extension allow to write much more.
We add uninit
before Type Name and we also add partiality to it - Partial Uninitialization.
We could use explicit uninitialization (together with (F)), or implicitly, but instead of (F) (that's why it is better remain Error if not all fields are filled)
struct S4 {a : i32, b : i32, c : i32, d : i32}
let s_bd : uninit.{a, c} S4 = S4 {b: 7, d: 9, ..uninit};
If we also use extension (E), then we could also write uninitialization for tuples and more flexible for Structs
let s_bd : uninit.{c} S4 = S4 {a: 3, b: 7, uninit c, d: 9};
let t_1 : uninit.{1} (i32, u16, f64, f32) = (uninit, 4, uninit, uninit);
let t_3 : (uninit i32, uninit u16, uninit f64, f32) = (uninit, uninit, uninit, 9.0f32);
let t_02 = (6i32, uninit 7u16, 8.0f64, uninit 9.0f32); // t_02 : (i32, uninit u16, f64, uninit f32)
If extension (E) is off, we must allow infer uninitialization from the type:
let t_3 : uninit.{0,1,2} (i32, u16, f64, f32) = (1, 7, 8, 9.0f32);
Sure, it is forbidden to read, and without (J) it is forbidden to move and to borrow uninitialized fields.
Now we could create self-Referential Types:
struct SR <T>{
val : T,
lnk : & T, // reference to val
}
let x : uninit.{lnk} SR<i32> = SR {val : 5, uninit lnk };
x.lnk = & x.val;
// x : SR<i32>;
This Uninitialization as we added is enough to extend by "referential uninitialization" (and not only for Partial Types, but for any types (Sized at least)), but due complexity it is not part of this extension.
(J+) independent sub-proposal Uninitialized Types or Movable and Referential Uninitialization
Most important: Uninitialized variable after initialization (droping Uninitialization), is no longer uninit
!
let a : i32;
// a : uninit i32;
a = 7;
// a : i32; uninit is "drop"/initialized
Uninitialized variable is movable: uninitialization is "moved" by move from sender to receiver.
let a : i32;
// a : uninit i32;
let b = a;
// b : uninit i32;
// a : i32; // not longer uninit, but moved
let c : &i32;
// c : uninit i32;
let d = c;
// d : uninit &i32;
// c : &i32; // not longer uninit, but moved
Referential Uninitialization is a bit complicated.
(1) Uninitialization is "moved" by move from sender to receiver (reference)
(2) Inner-Uninitialized Reference is always "exclusive", regardless if it mutable or not (till drop).
(3) Inner-Uninitialized dereferenceble Variable is always at least once write-only, regardless if it mutable or not
(4) Inner-Uninitialized Reference is forbidden to move (after initialization, reference is not longer inner-Uninitialized).
(5) Inner-Uninitialized Reference is forbidden to drop (after initialization, reference is not longer inner-Uninitialized).
let a : i32;
// a : uninit i32;
let b = &a;
// b : & uninit i32;
// a : i32; // not longer uninit, but exclusive borrowed!
*b = 7;
// b : & i32;
// now reference 'b' could be droped
drop(b);
// a == 7
Note: a : uninit & i32
is an uninitialized variable with referential type, but b : & uninit i32
is initialized reference to uninitialized variable
Uninitialized Parameters are similar to Referential Uninitialization
(1) Uninitialization must be written explicitly at Type
(2) If Uninitialized Parameter is a not-reference, then it behaves same as Uninitialized variable.
(3) If Uninitialized Parameter is an inner-uninitialized reference, then it behaves same as inner-uninitialized reference.
(4) If Uninitialized Parameter is an inner-uninitialized reference, then initialization must happens before return or together with return.
struct S4 {a : i32, b : i32, c : i32, d : i32}
impl S4 {
fn init_a(self : & unit.{a} Self.{a}) {
*self.a = 5;
}
}
Uninitialized arguments, again easy: uninitialization of argument and parameter must match! It is error if not.
struct S4 {a : i32, b : i32, c : i32, d : i32}
let s : uninit.{a} S4 = S4 { uninit a, b : 7, c : 33, d : 4};
s.init_a();
(K) sub-proposal, which could be added together or after (B) or/and together or after (A)
What if we wish to have more controls of partiality: control Structs inside Enums, or Enum inside Enums, Structs inside Structs, and so on?
We allow to use more detailed partiality!
fn foo (self : Self.{ A{1, 2.{B} } ) { /* .. */ }
(L) sub-proposal, which could be added together or after (B)
Unions are always is unsafe to use. Partiality could be extended to Unions
same as for Struct
.
But it do not make using units more safe.
The core Idea of this proposal is "Proxy Borrowing" - we borrow the whole variable, but borrow-checker pretends it borrow just permitted fields.
And Type-checker gives a mathematical guarantee, than all denied fields remain intact and all partly-immutable fields remain immutable!
And this mean, that Proxy Borrowing borrowing is fully safe and zero cost in binary.
(B) or (B + D) or (B + IJ) or (B + D + IJ)
Borrowing rules for partial types:
PermittedField
field borrowing rules are ordinary Rust rules. New variable borrows the whole variable (with partial type), but checker pretends it borrows just permitted fields of this variable.
Not-PermittedField
filed is always is ready to mutable and immutable borrow regardless if origin field is denied(by move, by reference, by borrow), is visible, is mutable.
When we write a code for partial borrow (or partly mutable borrow), the link of object itself returns, but borrow-checker borrows proxy of permitted fields only.
When we write a code for full (or partial) borrow of a partial borrowed reference, the link of object itself returns again, but borrow-checker, borrows proxy of proxy of permitted fields only.
This new mechanism of Proxy Borrowing is simple and universal.
struct S4 {a : i32, b : i32, c : i32, d : i32}
let s = S4 {a : 5, b: 6, c: 7, d: 8};
// s : S4
let r_sd = & s.{d};
// r_sd : & S4.{d}
//
// r_sd ~ Link(s);
// borrow-checker borrows ProxyLink(s.d)
let mut mr_sabc = &mut s.{a, b, c};
// mr_sabc : &mut S4.{a, b, c}
//
// mr_sabc ~ mut Link(s);
// borrow-checker: mut ProxyLink(s.a), mut ProxyLink(s.b), mut ProxyLink(s.c)
let rr_sbc = & mr_sabc.{b, c};
// rr_sbc : && S4.{b, c}
//
// rr_sbc ~ Link(mr_sabc);
// borrow-checker: ProxyLink(ProxyLink(s.b)), ProxyLink(ProxyLink(s.c))
let mut mrr_sa = &mut mr_sabc.{a};
// mrr_sa : &&mut S4.{a}
//
// mrr_sa ~ mut Link(mr_sabc);
// borrow-checker: mut ProxyLink(ProxyLink(s.a))
Second, but still important - syntax.
Minimal Partiality we could write:
Partiality: .{ PartialFields* }
PartialFields: PermittedField (, PermittedField )* ,?
PermittedField: IDENTIFIER | TUPLE_INDEX | * | _
If no implicit rules are used, then we could get rid of *
and _
quasi-fields.
PermittedField: IDENTIFIER | TUPLE_INDEX
(A)
The only one syntax needed to Enum - is update TypePath
into
TypePath: ::? TypePathSegment (:: TypePathSegment)* Partiality?
(B)
Syntax is needed to Struct Type - is update TypePath
(same as to Enum)
TypePath: ::? TypePathSegment (:: TypePathSegment)* Partiality?
For Tuple Type we need to update TupleType
TupleType: ( ) | ( ( Type , )+ Type? ) Partiality?
For Expression we need create new kind of Expression:
PartialExpression: Expression Partiality
and include it into ExpressionWithoutBdeny
:
ExpressionWithoutBdeny: ... | FieldExpression | PartialExpression | ...
(C)
No special syntax For this extension is needed.
(D)
We add Mutability
PartialMutability: mut Partiality?
And change mut
into PartialMutability
at IdentifierPattern
:
IdentifierPattern : ref? PartialMutability? IDENTIFIER (@ PatternNoTopAlt ) ?
If it implemented together or after (B), we change rest of mut
into PartialMutability
:
BorrowExpression: (&|&&) Expression | (&|&&) PartialMutability Expression
ReferenceType: & Lifetime? PartialMutability? TypeNoBounds
Function:ShorthandSelf: (& | & Lifetime)? PartialMutability? self
Function:TypedSelf: PartialMutability? self : Type
(E)
For Tuple Type we need to update TupleType
again:
TupleType: ( ) | ( ( DeniableSubType , )+ DeniableSubType? ) Partiality?
DeniableSubType: deny? Type
(F)
No special syntax for implicit partial initialization of Struct is don't needed.
For explicit partial initialization of Struct we update StructBase
.
StructBase: .. ( deny | Expression )
If (E) extension is on, then we also need to change StructExprField
and StructExprTuple
for Structs:
StructExprField: OuterAttribute * ( deny? IDENTIFIER | deny TUPLE_INDEX | (IDENTIFIER | TUPLE_INDEX) : Expression )
StructExprTuple: PathInExpression ( ( TupleElement (, TupleElement)* ,? )? )
If (E) extension is on, then we also need to change TupleExpr
:
TupleExpression: ( TupleElements? )
TupleElements : ( TupleElement , )+ TupleElement?
TupleElement: deny | deny? Expression
(G)
For updating Struct by several Structs, then we change StructBase
to StructBases
:
StructExprStruct: PathInExpression { (StructExprFields | StructBases)? }
StructExprFields: StructExprField (, StructExprField)* (, StructBases | ,?)
StructBases: StructBase (, StructBase )* ,?
StructBase: .. Expression
(H)
This extension is need to support ..
(or _
) "rest of fields" field name to infer Enum / Struct fields.
StructExprStruct: PathInExpression { ( .. | StructExprFields | StructBase)? }
(IJ)
First
PartialUninit: uninit Partiality?
and create UninitializedType
for "naked" Uninitialized Types and add it into and insert into TypeNoBounds
UninitializedType: PartialUninit TypeNoBounds
TypeNoBounds: ... | ReferenceType | UninitializedType | ...
If (E) extension is on or/and (F) extension is on we replace all deny
into deny | uninit
.
So, maximum changes are:
DenyOrUninit: deny | uninit
StructBase: .. ( DenyOrUninit | Expression )
StructExprField: OuterAttribute * ( DenyOrUninit? IDENTIFIER | DenyOrUninit TUPLE_INDEX | (IDENTIFIER | TUPLE_INDEX) : Expression )
TupleElement: DenyOrUninit | DenyOrUninit? Expression
(J+)
If it implemented together with (B), then no special syntax is needed.
Otherwise, we add just UninitializedType
.
UninitializedType: uninit TypeNoBounds
TypeNoBounds: ... | ReferenceType | UninitializedType | ...
(K)
It depends after which extension is switch on.
(not A, B or D)
If we wish to have "recursive" sub-partial Product-types for (B) and / or (D)
Partiality: .{ PartialFields* }
PartialFields: PartialField (, PartialField )* ,?
PartialField: PermittedField Partiality?
PermittedField: IDENTIFIER | TUPLE_INDEX | * | _
(A, not B, not D)
If we wish to have "recursive" sub-partial Enum-types for (A)
Partiality: .{ PartialFields* }
PartialFields: PartialField (, PartialField )* ,?
PartialField: PermittedField PartialSubFields?
PartialSubFields: { PartialSubField (, PartialSubField )* ,? }
PartialSubField: SpecificSubField Partiality
PermittedField: IDENTIFIER | TUPLE_INDEX | * | _
SpecificSubField: IDENTIFIER | TUPLE_INDEX
(A + B or A + D or A + B + D) We wish to have "recursive" sub-partial Enum-types for (A) and Struct-types (B)
Partiality: .{ PartialFields* }
PartialFields: PartialField (, PartialField )* ,?
PartialField: PermittedField (PartialSubEnumFields | Partiality)?
PartialSubEnumFields: { PartialSubEnumField (, PartialSubEnumField )* ,? }
PartialSubEnumField: SubEnumField Partiality?
PermittedField: IDENTIFIER | TUPLE_INDEX | * | _
SubEnumField: IDENTIFIER | TUPLE_INDEX
(L)
No special syntax is needed.
Third, but still important - Logic Scheme.
For pseudo-rust we suppose, partiality is a HashSet
of permitted field-names.
Common rules:
fn bar(v : SomeType.{'type_prtlty})
{ /* .. */ }
let v : SomeType.{'var_prtlty};
Then:
(1) If SomeType
is not supported type (Enum, Struct, Tuple) then Error.
(2) If partiality has no extra field-names type_prtlty.is_subset(full_prtlty)
it compiles, otherwise Error.
(3) If var_prtlty.is_subset(full_prtlty)
it compiles, otherwise Error.
(4) If type_prtlty.is_empty()
or var_prtlty.is_empty()
(if they are explicitly written as '.{}
') then Error
(A)
Let we have (pseudo-rust) and enm_param_prtlty
and enm_arg_prtlty
are HashSet
of permitted field-names:
fn bar(e : SomeEnum.{'enm_param_prtlty})
{ /* .. */ }
let e : SomeEnum.{'enm_arg_prtlty};
bar(e);
impl SomeEnum.{'enm_impl_prtlty} {
fn foo(self : Self.{'enm_slf_prtlty})
{ /* .. */ }
}
e.foo();
Then:
(1) If enm_arg_prtlty.is_subset(enm_param_prtlty)
it compiles, otherwise Error (inverse of Struct).
(2) If enm_slf_prtlty.is_subset(enm_impl_prtlty)
it compiles, otherwise Error (same as Struct).
(3) Let we have several implementations for same type, but different partiality. And all_enm_impl_prtlty
is an array
of each enm_impl_prtlty
.
If all_enm_impl_prtlty.iter().any(|&eip| enm_arg_prtlty.is_subset(eip))
it compiles, otherwise Error (same as Struct+).
(4) If 1 == all_enm_impl_prtlty.iter().fold(0, |acc, &eip| if enm_arg_prtlty.is_subset(eip) {acc+1} else {acc})
it compiles, otherwise ?Error.
We expect that just one "implementation" partiality is match and we choose it for calling a method.
(B)
Let we have (pseudo-rust) and st_param_prtlty
and st_arg_prtlty
are HashSet
of permitted field-names:
fn bar(s : SomeStructOrTuple.{'st_param_prtlty})
{ /* .. */ }
let s : SomeStructOrTuple.{'st_arg_prtlty};
bar(s);
let rsp = & s.{'expr_prtlty};
impl SomeStructOrTuple.{'st_impl_prtlty} {
fn foo(self : Self.{'st_slf_prtlty})
{ /* .. */ }
}
s.foo();
// (4) desugars into:
SomeStructOrTuple.{'st_impl_prtlty}::foo(s.{'st_slf_prtlty});
Then:
(1) If st_arg_prtlty.is_superset(st_param_prtlty)
it compiles, otherwise Error (inverse of Enum).
(2) If expr_prtlty.is_subset(st_arg_prtlty)
it compiles, otherwise Error.
(3) If st_slf_prtlty.is_subset(st_impl_prtlty)
it compiles, otherwise Error (same as Enum).
(4) Updating desugaring for self
(and Rhs
) variables.
Desugaring s.foo()
into SomeStructOrTuple.{'st_impl_prtlty}::foo(s.{'st_slf_prtlty})
.
(5) It has no sense to have several implementation of same product-type and different partiality.
(5+) Anyway let we have several implementations for same type, but different partiality. And all_st_impl_prtlty
is an array
of each st_impl_prtlty
.
If all_st_impl_prtlty.iter().any(|&sip| st_arg_prtlty.is_subset(sip))
it compiles, otherwise Error. (same as Enum).
(6+) If 1 == all_st_impl_prtlty.iter().fold(0, |acc, &sip| if st_arg_prtlty.is_subset(sip) {acc+1} else {acc})
it compiles, otherwise ?Error.
We expect that just one "implementation" partiality is match and we choose it for calling a method.
(C)
Let we have (pseudo-rust) and partiality "variables" are HashSet
of permitted field-names:
let s : SomeStructOrTuple.{'st_arg_prtlty};
impl SomeStructOrTuple.{'st_impl_prtlty}<Smv = Self> {
fn foo(self : Self.{'st_slf_prtlty}, smv : Smv.{'st_smv_prtlty})
{ /* .. */ }
}
s.foo();
// (2) desugars into:
SomeStructOrTuple.{'st_impl_prtlty}::foo(s.{'st_slf_prtlty}, s.{'st_smv_prtlty});
Then:
(1) Adding Smv = Self
(always Self
) as "same variable" general parameter.
(2) If st_smv_prtlty.is_subset(st_impl_prtlty)
it compiles, otherwise Error (same as for self
).
(3) Adding a rule for Smv
general parameter.
Desugaring s.foo()
into SomeStructOrTuple.{st_impl_prtlty}::foo(s.{st_slf_prtlty}, s.{st_smv_prtlty})
.
(D)
Let we have (pseudo-rust) and partiality "variables" are HashSet
of permitted field-names:
let mut.{'mut_var_prtlty} s : SomeStructOrTuple.{'st_var_prtlty};
let mrsp = &mut.{'mut_arg_prtlty} s;
Then:
(1) If mut_arg_prtlty.intersection(st_var_prtlty).is_subset(mut_var_prtlty)
it compiles, otherwise Error
(E)
No special rules requires.
(F)
If type is omitted, then in Expression:
let t = (some_expr, deny some_deny_expr);
some_deny_expr
is used for inferring Type only, expression itself is unused!
(G)
Let we have (pseudo-rust) and partiality "variables" are HashSet
of permitted field-names:
let s1 : SomeStruct.{'st1_prtlty};
let s2 : SomeStruct.{'st2_prtlty};
/* .. */
let sN : SomeStruct.{'stN_prtlty};
let snew : SomeStruct = SomeStruct {..s1, ..s2, /* */ ..sN};
let sprt = SomeStruct {..s1, ..s2, /* */ ..sN, .. deny};
Then:
(1) If for any J > I
we have st<I>_prtlty.is_disjoint(st<J>_prtlty) == true
it compiles, otherwise Error ;
(2) If for snew
variable full_prtlty.difference(st1_prtlty).difference(st2_prtlty)/* */.difference(stN_prtlty).is_empty()
it compiles, otherwise Error.
(H)
No special rules requires.
(IJ + J+)
Let we have (pseudo-rust) and partiality "variables" are HashSet
of permitted field-names:
let s : uninit.{'uninit_var_prtlty} SomeStructOrTuple.{'st_var_prtlty};
let rsp : & uninit.{'uninit_arg_prtlty} = & s.{'st_expr_prtlty};
// `s` change uninitialization after consuming into
// s : uninit.{'uninit_after_prtlty} SomeStructOrTuple.{'st_var_prtlty};
Then:
(1) If uninit_var_prtlty.is_subset(st_var_prtlty)
it compiles, otherwise Error
(2) If uninit_arg_prtlty.is_subset(uninit_var_prtlty)
it compiles, otherwise Error
(3) If uninit_after_prtlty.is_subset(uninit_var_prtlty)
it compiles, otherwise Error
(4) If uninit_var_prtlty.intersection(st_expr_prtlty).is_subset(uninit_arg_prtlty)
it compiles, otherwise Error
(5) If uninit_var_prtlty.difference(st_expr_prtlty).is_subset(uninit_after_prtlty)
it compiles, otherwise Error
If (F) is on, then if type is omitted in Expression:
let t = (some_expr, uninit some_uninit_expr);
some_uninit_expr
is used for inferring Type only, expression itself is unused!
(K)
No special rules requires.
(L)
No special rules requires.
- it is definitely not a minor change
- type system became much more complicated
(A) Proposals that are alternatives to Partial Sum Types in a whole:
- Enum variant types #2593
- Enum Variant Types lang_t#122
(B) A lot of proposals that are alternatives to Partial Product Types in a whole:
- Partial Types (v2) #3426
- Partial Mutability #3428
- Partial Types #3420
- Partial borrowing issue#1215
- View patterns internals#16879
- Permissions #3380
- Field projection #3318
- Fields in Traits #1546
- ImplFields issue#3269
(H) Proposals of Anonymous Types:
- Structural Record #2584
- Anonymous variant types, a minimal ad-hoc sum type#2587
- Disjoins (anonymous enums) #1154
- Anonymous enum types called joins, as A | B #402
- Anonymous enum types (A|B) take 2 #514
(IJ) Proposals of partial initializing
(any.details) Alternative for another names or corrections for Partial Types.
Most languages don't have such strict rules for references and links as Rust, so this feature is almost unnecessary for them.
(C) Security of using this extension. Is it secure not to check the origin if variable is the same if we explicitly write associated function?
impl Bar<Smv = Self>{
fn foo(self : &mut Self::{x}, smv: & Smv::{y}) { /* */ }
}
Bar::foo(&mut bar.{x}, & bar.{y}); // Ok
Bar::foo(&mut bar.{x}, & baz.{y}); // Error? Ok?
I think it is insecure, error, but who knows. If yes, then Ok.
Any of modules (A), (B), (C?), (D), (E), (F), (G), (H), (IJ), (K), (L).