Skip to content

RFC: Mixed mutable variables #3427

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
345 changes: 345 additions & 0 deletions text/0000-mixed_mutable_variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
- Feature Name: `mixed_mutable_variables`
- Start Date: 2023-04-28
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)


# Summary
[summary]: #summary

Mixed Mutable Variables proposal is an alternative and addition to Partial Types proposal.

This proposal suggest to add new type of variables - Mixed mutable variables, including Mixed mutable references.

Advantages: maximum flexibility, usability and universality.


# Motivation
[motivation]: #motivation

Safe, Flexible controllable partial parameters for functions and partial consumption (including partial not borrowing) are highly needed.

Partial Types without Mixed mutable variables has no full control on parameters, where Mixed mutable references are welcomed.

Mixed Mutable Variables are new kind of Types, which are possible due Partial Types and they need twice memory on each mixed and pseudo-unmixed variable (before optimization).

We could apply _theoretically_ this extension to all Product Types (`PT = T1 and T2 and T3 and ...`) and it fully covers all variants of possible uses of Product Types.

So, most promised candidates are Structs and Tuples.

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

For Product Types `PT = T1 and T2 and T3 and ...` (structs, tuples) we assume they are like structs with controllable field-access.

Mixed mutable variables are similar to partial types, where `^mut` - mutable field, `^const` - immutable field and here `mut = ^allmut = ^{^mut *}`, `<const> = ^allconst = ^{^const *}`.

Its is possible to have mixed mutable variables and mixed mutable borrowing and (immutable)references:
```rust
let ^max p1 = Point {^mut x:1.0, ^mut y:2.0, was_x: 4.0, was_y: 5.0, state: 12.0};
// infer mutability from Right expression
// ^{^mut x, y, ^const was_x, was_y, state} p1 : Point

let ^type p2 : ^{^mut x, y} Point = Point {x:1.0, y:2.0, was_x: 4.0, was_y: 5.0, state: 12.0};
// infer mutability from mutability, lifted to type
// ^{^mut x, y, ^const was_x, was_y, state} p2 : Point

let ref_p1 = &^max p1;
// set mixed-mutable reference
// ref_p1 : & ^{^mut x, y, ^const was_x, was_y, state} Point

let refnr_p2 = &^{^mut x} p2;
// set mixed-mutable reference by specific detailed mutability
// refnr_p2 : & ^{^mut x, ^const y, was_x, was_y, state} Point

let refnr2_p2 = & refnr_p2;
// pseudo-unmixed reference
// refnr2_p2 : && Point
```

We could write effective mixed-mutable references-arguments:
```rust
let mut p1 : Point2 = Point2 {x:1.0, was_x:2.0};

fn pntm_store (&^type p : & ^{^const x, ^mut was_x} Point2) {
*p.was_x = *p.x;
}

pntm_store(&^arg p1); // effective reference
```

We could get full control with Partial Types (A) together: we could write parallel using function implementation, which update either `x` or `y` and both read `state` field.
```rust
impl {
pub fn mx_rstate(&^type self : &^{^mut x, ^const state, _} %{x, state, %any} Self)
// infer mutability from mutability, lifted to type
{ /* ... */ }

pub fn my_rstate(&^type self : &^{^mut y, ^const state, _} %{y, state, %any} Self)
// infer mutability from mutability, lifted to type
{ /* ... */ }

pub fn mxy_rstate(&^type self : &^{^mut x, y, ^const state, _} %{x, y, state, %any} Self)
// infer mutability from mutability, lifted to type
{
/* ... */
self.{x, state}.mx_rstate();
/* ... */
self.{y, state}.my_rstate();
/* ... */
}
}

p1.mxy_rstate();
```
We need for this ... "mixed-mutable" references! Wow!

Anyway, it is easy, useful and universal!


# Reference-level explanation

- [Mixed Mutability]
- [Fields names and field-mutability]
- [Detailed mutability]
- [Mutable Filter]
- [Mutable binding on Let-assignment]
- [Mixed mutable Initialized Variables]
- [Mixed Parameters]
- [Mixed referential arguments]
- [Mixed Parameters in Traits]
- [Private fields]
- [Enum Type]

## Mixed Mutability

I propose to extend variable sharing by extending mutability, like `^allconst var : (i16, &i32, &str)`.

If we omit to write mutability, it means `^allconst` mutability, `mut = ^allmut` synonym. Symbol (`^` circumflex) means waving or changeability.

Mutability has similar names and meanings as lifetimes/accesses: `mut = ^allmut`(all mutable, soft keyword), `^_`(don't care how mutable is, soft keyword), `^allconst` (all immutable, soft keyword) and any other `^a`(some "a" mutability). But in most cases we use detailed mutability `^{}`.

## Fields names, field-access and field-mutability

Fields names inside detailed access:
- named field-names for structs(and units)
- unnamed numbered for tuples
- `*` "all fields" quasi-field
- `_` "rest of fields" quasi-field
- `self` pseudo-field for unsupported types

Filed-mutability can be in 2 values:
- `^mut` - field is mutable,
- `^const` - field is immutable.

`^mut` filed-mutability: permit to read, to write, to mutable-borrow, to immutable-borrow, to move.

`^const` filed-mutability: permit to read, to immutable-borrow, to move.

## Detailed access and mutability

Detailed field-mutability has next structure: `^{^field-mutability1 field-name1, ^field-mutability2 field-name2, ..}`

Detailed mutability is a set of unordered and unsorted field-names and their filed-mutability.

First `^field-mutability1` field-mutability if it is omitted means `^const`. First `^field-mutabilityM` field-mutability for _first omitted_ filed-name means `^opposite`.

The rest of omitted `^field-mutabilityN` field-mutability means `^same` (same as previous `^field-mutabilityN-1`)

| `^field-mutabilityN-1` | `^same` | `^opposite` |
|------------------------|-----------|-------------|
| `^mut` | `^mut` | `^const` |
| `^const` | `^const` | `^mut` |

Examples:
```rust
// rpfull : &^{was_x, was_y} Point
// same as
// rpfull : &^{^mut x, y} Point
// same as
// rpfull : &^{was_x, was_y, ^mut x, y} Point
// same as
// rpfull : &^{^mut x, ^mut y, ^const was_x, ^const was_y} Point
```

Sharing mutability `&mut` is a synonym for `& ^allmut` which is a shortcut for `& ^{^mut *}`. Sharing mutability `&` is a synonym for `& ^allconst` which is a shortcut for `& ^{^const *}`.

## Access-filter and Mutable Filter

Rust consumer use same names for action and for type clarifications, so we follow this style.

Mutable filter is an action, which is looks like detailed mutability and it is written left to access-filter at consuming (moving, borrowing, referencing, initializing, returning, pick fields).

| ↓filter / →var mut+access | `^mut %_` | `^const %permit` | `^const %deny/%miss` | `^const %ignore` |
|---------------------------|-----------|------------------|----------------------|------------------|
| `^mut` | `^mut` | !ERROR | `^mut` | !ERROR |
| `^const` | `^const` | `^const` | `^const` | `^const` |

It is allowed to write explicitly specific mutability-filter with same rules as detailed mutability.
Default filter on consumption if filter is omitted for:
- `var ~ ^max var`
- `& var ~ & ^allconst var`
- `&mut var ~ & ^allmut var`

where filters are based on variable field-mutability:

| ↓var mutability | `^max` |
|-----------------|-----------|
| `^mut` | `^mut` |
| `^const` | `^const` |

### Mutable binding on Let-assignment

Mutable binding is a special consumer on Let-assignment. If it is a value (not a reference) mutable binding could set any asked mutability!

Default Let-filter for:
- `let var = &... ~ let ^max var = &...`
- `let var = ... ~ let ^allconst var = ...`
- `let mut var = ... ~ let ^allmut var = ...`

So, if we wish to have same mutability, we must write explicitly:
```rust
struct Point2 {x: f64, y: f64}

let ^max pfull = Point2 {^mut x: 4.0, y: 5.0};
// ^{^mut x, ^const y} pfull : Point2
```

Or write specific mutability directly at `let`:
```rust
let ^{^mut x} pfull = Point2 {x: 4.0, y: 5.0};
// ^{^mut x, ^const y} pfull : Point2
```

Or we could lift detailed mutability into the type and use `^type` mutability
```rust
let ^type pfull : ^{^mut x} Point2 = Point2 {x: 4.0, y: 5.0};
// ^{^mut x, ^const y} pfull : Point2
```

## Mixed mutable Initialized Variables

Mixed Mutable Initialized Variable has next structure:
- for structs: `^mutability-filter Construct{^field-mutability1 field-let-mutability1: value1, ^field-let-mutability2 field-name2: value1, ..};`
- for tuples: `^mutability-filter (^field-let-mutability1 field-name1: value1, ^field-let-mutability2 field-name2: value1, ..);`

Access filter `^mutability-filter` if it is omitted means `^max`.

All `^field-let-mutabilityN` field-access if they are omitted mean `^max`. We assume, that Literals and out-variable values has `^const` mutability.

## Mixed Parameters

Mixed Parameters has mutable binding-mutable action, like on let-expressions
```rust
impl {
pub fn mx_rstate(&^type self : &^{^mut x, ^const state, _} %{x, state, %any} Self)
{ /* ... */ }

pub fn my_rstate(&^type self : &^{^mut y, ^const state, _} %{y, state, %any} Self)
{ /* ... */ }

pub fn mxy_rstate(&^type self : &^{^mut x, y, ^const state, _} %{x, y, state, %any} Self)
{
/* ... */
self.{x, state}.mx_rstate();
/* ... */
self.{y, state}.my_rstate();
/* ... */
}
}
```

## Mixed referential arguments

For function argument if it a value is meaningless to set special mutability, so omitted argument mutability is `^max`.

But for reference it is possible to write mixed borrowing `&^arg`, but it refers not to variable mutability, but to parameter referential mutability, so we could use it in arguments consumption only! It is an compile error if `&^arg` is written outside of contents!

| param mutability | `&^arg` |
|------------------|----------|
| `^mut` | `^mut` |
| `^const` | `^const` |

Implementations always consumes `self` by `&^arg` filter!

```rust
let mut p1 : Point2 = Point2 {x:1.0, was_x:2.0};

fn pntm_store (&^type p : & ^{^const x, ^mut was_x} Point2) {
*p.was_x = *p.x;
}

pntm_store(&^arg p1); // effective reference

pntm_store(&mut p1); // still ok

pntm_store(& p1); // error
```

## Mixed Parameters in Traits

Mixed Parameters in Traits could have generalized sharing mutability variants:
```rust
trait Saveable {

fn val_store<^a, %b>(&^type self: & ^a %b Self);

fn val_restore<^a, %b>(&^type self: & ^a %b Self);
}
```

## Private fields

Rust allows to control outer mutability of private fields via bindings or mutable and immutable borrowing. So, it is secure to control mutability of private fields without extra rules.

## Enum Type

What's about Enums? Enum is not a "Product" Type, but a "Sum" Type (`ST = T1 or T2 or T3 or ..`).

So, this proposal ignore this type!


# Drawbacks
[drawbacks]: #drawbacks

- it is definitely not a minor change
- It is highly recommended to deprecate operator `^` as a bitwise xor-function (it is still no ambiguities to write "`\s+^\s+`"), and replace it with another operator (for example: `^xor` / `xor`) to not to be confused by sharing mutability. But it is not a mandatory.


# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

(A) A lot of proposals that are alternatives to Partial Types in a whole:
- Partial Types v2 [#3426](https://github.com/rust-lang/rfcs/pull/3426)
- 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)
- Field projection [#3318](https://github.com/rust-lang/rfcs/pull/3318)
- Fields in Traits [#1546](https://github.com/rust-lang/rfcs/pull/1546)
- ImplFields [issue#3269](https://github.com/rust-lang/rfcs/issues/3269)

(any.details) Alternative for another names or corrections for Mixed mutable variables.
- `^allconst` or `^constall` name (and `^allmut` or `^mutall`)


# 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

None known.


# Future possibilities
[future-possibilities]: #future-possibilities

Mixed mutable variables could extend and add flexibility to Partial Types.

Loading