plein
August 26, 2023, 3:27pm
1
This is a decade old issue and I think it's just one step to being solved, but at its current state it's not ergonomic and looks ugly.
#[derive(Default)]
struct Woo {
f1: String,
f2: String,
f3: String,
}
fn main() {
let mut t = Woo::default();
let k = &t.f3;
// foo(&mut t);
// Woo::moo(&mut t);
foo2(&mut t.f1, &mut t.f2);
dbg!(k);
}
fn foo(Woo { f1, f2, .. }: &mut Woo) {
// this is rejected
}
fn foo2(f1: &mut String, f2: &mut String) {}
impl Woo {
pub fn moo(Self { f1, f2, .. }: &mut Self) {
// rejected
// also doesn't appear as t.moo
}
}
http://smallcultfollowing.com/babysteps/blog/2021/11/05/view-types/
opened 05:51PM - 20 May 22 UTC
First of all, I am well aware of [this issue](https://github.com/rust-lang/rfcs/… issues/1215), I looked through people's ideas but I did not like any of them. This is my approach to the problem.
# Rationale
Borrow a checker is an amazing tool offered by the compiler that catches a scary amount of bugs. It also does not disallow you from anything as with enough context, a borrow checker can let a lot of code pass and let you write the natural way, only protesting when necessary. This of course only really applies when you write everything into a singular function.
Within the boundaries of a function, the compiler is excellent, but when you borrow something from another function, there is not enough information to prove anything. You can only borrow everything or nothing, which tends to force users into two scenarios.
```rust
// instead of
self.do_something();
// we have to resort to
Self::do_something(&mut self.this, &self.that, &mut self.and_even_this);
// other option is
{
// just inline if function is simple enough
// borrow checker now gets enough context
}
```
# Hacks
Both of the above can be partially solved by hardcoding a macro.
```rust
macro_rules! self_do_something {
($self:expr) => {
Self::do_something(&mut $self.this, &$self.that, &mut $self.and_even_this)
};
}
```
This results in a lot less typing.
```rust
// now we can say
self_do_something!(self)
```
There still stays the problem of declaring the method with numerous parameters. We also have to write the no-brainer macro and we lose the `self` parameter grouping things together. The next step would be packing all borrowed states into a struct, and constructing them inside the macro.
```rust
self_some_view!(self).do_something()
```
As you can see with some boilerplate and the use of ugly macros, you can use View Types already.
# New Kind Of Type
A new type of declaration needs a keyword. I cannot decide what kind of keyword it should be so I will refer to it by #keyword
I propose adding the so-called View types that would be types bound to some parent type, restricting access and mutability. The header of the type can look similar to trait implementation.
```rust
$($vis)? $(unsafe)? #keyword $name:ident for $parent:ty { ... }
```
`name` signifies the name of view type. All generic parameters have to be inherited from the parent. `parent` would be the target type, the view is capturing. This can be any type and even another View. `unsafe` is an annotation that makes accessing view type fields an unsafe operation. Now the body.
```rust
<...> {
// the `as ...` is for optionally specifying the view of given field so nesting is possible
$($($vis)? $(mut)? $($field_name:ident|$field_number:literal) $(as $view:ident)?),* $(,)?
}
```
Let's give an example of View type in action.
```rust
// immutable struct:
struct A {
value: i32,
}
#keyword ImmA for A {
pub value,
}
// main feature of view types is ability to implement methods that all parents inherit.
impl ImmA {
// here we return owned view type, since `value` is not mut,
// user can read but not modify the field (without using `unsafe`)
pub fn new(value: i32) -> Self {
A { value }
}
}
fn test() {
// Here the `new` can be also called on `A`
let mut a = A::new(20);
a.value = 10;
^^^^^^^^^^^^^ invalid
}
```
Let's get a little bit more complex.
```rust
struct Vec<T> {
data: VecData<T>, // pointer and cap
len: usize,
}
impl<T> Vec<T> { ... }
pub unsafe #keyword MutableVecLen for Vec<T> {
pub mut len,
}
pub #keyword VecLen for Vec<T> {
// notice that view type can alter visibility
pub len,
}
#keyword VecIter for Vec<T> {
mut data,
len,
}
impl<T> VecIter<T> {
fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
// we can now fully access `data` but only immutably access `len`
...
}
}
fn test() {
let mut vec = vec![1, 2, 3];
assetr_eq!(vec.len, 3); // no need to call `len()`, or any boilerplate getter for that matter
unsafe {
vec.len = 2; // valid within the unsafe block
vec.len = 3; // lets put it back
}
vec.iter_mut().for_each(|e| *e = vec.len); // `iter_mut` borrows the `len` immutably
}
```
# implementation
I do not have a deep understanding of how rust solves things and how would this fit into the codebase or performance implications so bear with me.
The rough idea for visibility would be traversing subtypes until they contain the given field, if no field with correct visibility is found, emit an error. Mutability is a bit different, You cannot define field mutable if it's already defined as immutable in the parent. When type-checking view should be always assignable from the parent and methods callable from any parent up to the View itself. Differentiating `unsafe` and safe View can be difficult but possible. The most restricting View should be used when inferring. Views are similar to Traits in a way they relate to types. You should be only able to define the view inside the crate target type is defined unless it is possible to restrict this less. Views also have to be distinct, this can be determined by hashing of some sort and lookup.
# uncertainty
- Should Views require import to be used?
- Should we be able to implement traits on View types?
# advantages
- View types can make the Type system stronger and a lot more expressive.
- APIs can be improved and made easier and safer to use.
- Code can be organized into methods with more ease.
# alternatives
Some functionality can be simulated by solutions seen in hacks. Mutability and publicity as well but View types seem like idiomatic reduction of boiler-plate.
There is still no RFC on this issue. It affects code structuring significantly.
1 Like
VitWW
August 27, 2023, 1:57pm
2
Are you talking about partial types?
I have already add 2 RFCs related to topic, but they are fluid in changes and I've already close 2 RFC related to topic (which I changed several times).
So, people admit me to find feedback here, your ideas are welcomed, as your alternatives and so on
UPD: Partial Types (v3)
Partial Types (v2) (rfc#3426 ) and
Partial Mutability (rfc#3428 )
This is a universal and full solution to "partial borrow" problem.
It is based on fact, that on Product Types (PT = T1 and T2 and T3) like Strcts and Tupl…
I was thinking about possible solutions comparing my thoughts with existed solutions. Probably, I found out how we can achieve partial borrowing. So, for now I stress test my solution. If all will going fine, I will public it later
Previously I wanted to comment my thoughts here (94 lines of code + text), but when I met commentary on the issue (below), I tried to read all of existing variants and analyze them.
Entry point: https://github.com/rust-lang/rfcs/issues/1215
The best variant for now (in my opinion) is permissions: Permissions by DasLixou · Pull Request #3380 · rust-lang/rfcs · GitHub
The next one is in issue above, but I don't like pattern matching at self argument, so I don't really like it
Thank you for remind
Okay, I'm done, you can check my idea out in the next topic:
So, in recent topic , I wrote, that I develop my idea about how we can achieve partial mutability, here is results:
Here I want to gather some opinion about draft document, resolve some related question, gather more critic and, maybe, try to push this to rust rfc repository.
I've worked on this for a while and it's not perfect, but in general I think all is good.
Maybe, proposed syntax is not that you expected, but I hope that you will not hate this, at least.
If you interested in, please, r…
system
Closed
December 4, 2023, 12:29pm
5
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.