-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: Partial Types (v3) #3736
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
base: master
Are you sure you want to change the base?
RFC: Partial Types (v3) #3736
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,395 @@ | ||
- Feature Name: `partial_types` | ||
- Start Date: 2024-12-06 | ||
- RFC PR: [rust-lang/rfcs#3736](https://github.com/rust-lang/rfcs/pull/3736) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
This proposal is universal flexible tool to work **safe** and **zero cost binary** with partial 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. | ||
|
||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
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 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** (without using `unsafe`). | ||
```rust | ||
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. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Partiality of type (or partial type access) is written as `Path.{fld1, fld2, fld3}` after Path (Type name), where `fld1`, `fld2`, .. are only permitted read (and maybe to write if variable is `mut`) fields of this type, the rest of fields are forbidden to read and write and unset. | ||
|
||
But the same time fields that are forbidden to read and write it is totally Ok to borrow, re-borrow, move, re-move, without any consequences - because the Compiler guarantee that in safe mode it is impossible to use such fields. It is a compile error if someone try to access it. | ||
|
||
## Partial Structs and Tuples | ||
|
||
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, fld3}`, where `fld1`, `fld2`, .. are permitted fields of this type, the rest of fields are forbidden. | ||
|
||
One step to partial borrows Structs and Tuples. | ||
```rust | ||
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. | ||
|
||
It is easy to write functions, which consume partial parameters: | ||
```rust | ||
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)! | ||
VitWW marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Pseudo-rust: | ||
```rust | ||
fn ref_xy (self : & Self.{ x, y}) -> &f64 { | ||
/* */ | ||
} | ||
|
||
p1.ref_xy(); | ||
// 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. | ||
```rust | ||
// 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.{a}); // s.{a} - error | ||
do_sab(s.{b}); // s.{b} - error | ||
do_sab(s.{a, b}); // s.{a, b} - Ok | ||
do_sab(s.{a, b, c}); // s.{a, b, c} - Ok | ||
do_sab(s); // s.{*} - Ok | ||
``` | ||
|
||
|
||
# Reference-level explanation | ||
|
||
The core Idea of this proposal is "Proxy Borrowing" - we borrow the whole variable, but borrow-checker pretends it borrow just permitted/allowed fields. | ||
|
||
Automatically Type-checker gives a mathematical guarantee, because all denied/forbidden fields remain intact! | ||
|
||
And this mean, that Proxy Borrowing borrowing is fully **safe** and _zero cost_ in binary. | ||
|
||
## Proxy Borrowing | ||
|
||
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 borrow regardless if origin field is denied(by move, by reference, by borrow). | ||
|
||
When we write a code for full or partial borrow, the link of object itself returns, but borrow-checker checks to borrow of permitted fields only. | ||
|
||
This new mechanism of is simple and universal. | ||
|
||
```rust | ||
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} | ||
// | ||
// borrow-checker check just for &s.d | ||
|
||
let mut mr_sabc = &mut s.{a, b, c}; | ||
// mr_sabc : &mut S4.{a, b, c} | ||
// | ||
// borrow-checkercheck just for &mut s.a, &mut s.b, &mut s.c | ||
|
||
let rr_sbc = & mr_sabc.{b, c}; | ||
// rr_sbc : && S4.{b, c} | ||
// | ||
// borrow-checker check just for &mr_sabc.b, &mr_sabc.c | ||
|
||
let mut mrr_sa = &mut mr_sabc.{a}; | ||
// mrr_sa : &&mut S4.{a} | ||
// | ||
// borrow-checker check just for &mut mr_sabc.a | ||
``` | ||
|
||
## Syntax | ||
|
||
Second, but still important - syntax. | ||
|
||
### Partiality Syntax | ||
|
||
Minimal Partiality we could write: | ||
``` | ||
Partiality: .{ PartialFields* } | ||
PartialFields: PermittedField (, PermittedField )* ,? | ||
PermittedField: IDENTIFIER | TUPLE_INDEX | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 commentThe 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:
If we wish to describe partial structs with partial structs inside, we must have a bit more complex Partiality:
|
||
``` | ||
|
||
### Partial Struct syntax | ||
|
||
Syntax is needed to Struct Type - is update `TypePath` | ||
``` | ||
TypePath: ::? TypePathSegment (:: TypePathSegment)* Partiality? | ||
``` | ||
|
||
### Partial Tuple syntax | ||
|
||
For Tuple Type we need to update `TupleType` | ||
``` | ||
TupleType: ( ) | ( ( Type , )+ Type? ) Partiality? | ||
``` | ||
|
||
### Partial Expression syntax | ||
|
||
For Expression we need create new kind of Expression: | ||
``` | ||
PartialExpression: Expression Partiality | ||
``` | ||
|
||
and include it into `ExpressionWithoutBdeny`: | ||
``` | ||
ExpressionWithoutBdeny: ... | FieldExpression | PartialExpression | ... | ||
``` | ||
|
||
|
||
## Logic Scheme | ||
|
||
Third, but still important - Logic Scheme. | ||
|
||
For pseudo-rust we suppose, partiality is a `HashSet` of permitted field-names. | ||
|
||
Common rules: | ||
```rust | ||
fn bar(v : SomeType.{'type_prtlty}) | ||
{ /* .. */ } | ||
|
||
let v : SomeType.{'var_prtlty}; | ||
``` | ||
Then: | ||
|
||
(1) If `SomeType` is not supported type (neither Struct nor 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 | ||
VitWW marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Partial Struct and Tuples Logic Scheme | ||
|
||
|
||
Let we have (pseudo-rust) and `st_param_prtlty` and `st_arg_prtlty` are `HashSet` of permitted field-names: | ||
```rust | ||
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. | ||
|
||
(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. | ||
|
||
(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. | ||
|
||
(6) 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. | ||
|
||
(8) 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. | ||
|
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
- it is definitely not a minor change | ||
- type system became much more complicated | ||
|
||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
A lot of proposals that are alternatives to Partial Product Types in a whole: | ||
- Partial Types (v2) [#3426](https://github.com/rust-lang/rfcs/pull/3426) | ||
- Partial Mutability [#3428](https://github.com/rust-lang/rfcs/pull/3428) | ||
- Partial Types [#3420](https://github.com/rust-lang/rfcs/pull/3420) | ||
- Partial borrowing [issue#1215](https://github.com/rust-lang/rfcs/issues/1215) | ||
- View patterns [internals#16879](https://internals.rust-lang.org/t/view-types-based-on-pattern-matching/16879) | ||
- Permissions [#3380](https://github.com/rust-lang/rfcs/pull/3380) | ||
- Fields in Traits [#1546](https://github.com/rust-lang/rfcs/pull/1546) | ||
- ImplFields [issue#3269](https://github.com/rust-lang/rfcs/issues/3269) | ||
|
||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
Most languages don't have such strict rules for references and links as Rust, so this feature is almost unnecessary for them. | ||
|
||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
It is better to have 2 ways to write partiality: a list with just "allowed" fields and just "forbidden" fields for better ergonomics | ||
|
||
```rust | ||
let t1 = s10.{fld1, fld2, fld3, fld4, fld5, fld6, fld7, fld8}; | ||
// just "allowed" fields | ||
// t1 : S10.{fld1, fld2, fld3, fld4, fld5, fld6, fld7, fld8} | ||
|
||
let t2 = S10.{off fld9, fld10}; | ||
// just "forbidden" fields | ||
// t2 : S10.{fld1, fld2, fld3, fld4, fld5, fld6, fld7, fld8} | ||
``` | ||
|
||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
Adding "partiality" opens wide variety of future possibilities. | ||
|
||
|
||
## Partial Mutability | ||
|
||
*partly independent sub-proposal*. | ||
|
||
For full flexibility of using partial borrowing partial mutability is needed! | ||
|
||
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: | ||
```rust | ||
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. | ||
|
||
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: | ||
```rust | ||
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! | ||
|
||
Example with full flexibility of using partial borrowing together with partial mutability | ||
|
||
```rust | ||
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 | ||
/* ... */ | ||
} | ||
} | ||
``` | ||
|
||
|
||
## Explicit Off Fields | ||
|
||
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 `off` before denied field. | ||
```rust | ||
let t :: (i32, &u64, f64, u8).{1,3}; | ||
// same as | ||
let t :: (off i32, &u64, off f64, u8); | ||
``` | ||
|
||
This extension is not just pretty, but useful for Tuples. | ||
|
||
|
||
## Partial Types to Sum Types (Enums) | ||
|
||
Partial Types extension gives to Sum Types (`ST = T1 or T2 or T3 or ..`), Enums first of all, a good tool for "partial functions". | ||
```rust | ||
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); | ||
``` |
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.
This is not really a motivation, this is a sketch of your solution. Here you should instead be describing the problem that you have today, first. Starting with "this is a generalization of other proposals" is not a good way to motivate it.
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.
Ok. I've update motivation a bit: