pre-RFC: "field" as an item and "borrows"

as I'm not a native english speaker plz tell me if there is any part that is difficult to read and understand

Summary

field is an item that represents field in struct.

borrows is a set of field that supports borrowing.

I'm not convinced about name borrows. maybe there will be a better name for it plz suggest me

Motivation

field as item

this allows traits to have field which needs to be implemented when implementing trait

fields-in-traits

and it is quite similar to niko's fields-in-traits-rfc

borrows

this helps partial borrowing

related issue

Guide-level explanation

field

field is is interface that represents field of struct

struct Foo {
    x: usize,
    y: usize
}

there's a field x, y in Foo so Foo::x and Foo::y is defined field

field can be accessed like this

fn field1() {
    let foo = Foo {
        x: 42,
        y: 39,
    };
    
    assert_eq!(
        foo.(Foo::x), //and the value of Foo::x can be accessed with Foo
        foo.x //in short
    );
}

field can be field of field of field of .......

struct Bar {
    foo: Foo,
}

so Bar::foo.x and Bar::foo.y is field of Bar

fn field_of_field {
    let bar = Bar {
        foo: Foo {
            x: 1,
            y: 0
        }
    };
    assert_eq!(1, bar.(Bar::foo.x));
}

borrows

borrows is a set of field to borrow

borrows visualises partial borrow

it can be declared using borrows keyword

borrows can have public visibility

putting borrows in declaration of borrows will flatten it

so Foo::{ .. } and Foo::{ Foo::{ .. } } is same

pub borrows borrows1 = Foo::{
    //you can put mutability
    mut x,
    y
}

remaining fields with ..

this will borrow x as immutable and others as mutable

pub borrows negative_borrow1 = Foo::{
    x,
    mut ..
}

negative borrow

this will borrow others except x

pub borrows negative_borrow1 = Foo::{
    !x,
    ..
}

borrowing field of field

this will borrow foo.x

pub borrows borrow3 = Bar::{
    foo.{
        x
    }
}

in short

pub borrows borrow3 = Bar::{
    foo.x
}

examples

//this will borrow Bar::foo with full mutability
pub borrows borrows2 = Bar::{
    mut foo
}

//this will borrow Bar::foo as immutable and borrow Bar::foo.x as mutable
pub borrows borrow3 = Bar::{
    foo.{
        mut x,
        ..
    }
}

//this will borrow Bar::foo as mutable except Bar::foo.x(immutable)
pub borrows borrow4 = Bar::{
    foo.{
        x,
        mut ..
    }
}

pub borrows borrow5 = Foo::{
    mut x,
    ..
}

//this will borrow Bar::foo as immutable and borrow Bar::foo.x as mutable
pub borrows borrow6 = Bar::{
    foo.{ borrow5 }
}

borrowing

borrows used in partial borrow

you don't need to do this as current borrow checker automatically implements partial borrow in this case

fn borrows_use() {
    let foo = Foo {
        x: 42,
        y: 39
    };
    
    let foo_part1 = &foo.{ x }; //borrow only x
    //using foo_part1.y will cause borrow check error
    
    let foo_part2: &Foo::{ y } = &foo; //borrow only y
    //using foo_part2.x will cause borrow check error
}

field and borrows in impl block

impl Foo {
...

this example makes private field Foo::x accessible out of the module with foo.x_another

field can have public visibility

field can be declared with field keyword

declared name should not conlfict with field name in Foo

...
    pub field x_another = Self::x;
...

you can borrow all fields of Foo like this

...
    fn field2(&self.{ .. }) {
        self.field3(); //you can borrow all fields of Foo so you can call field3
        self.field4(); //you can borrow Foo::x so you can call field4
    }
...

borrowing all fields can be simplified

...
    fn field3(&self) {
    
    }
...

borrow only Foo::x

...
    fn field4(&self.{ x }) {
        field6(self) //you can borrow Foo::x so you can call field6
    }
...

borrowing fields in typed param

...
    fn field5(foo: &Foo::{ x }) {
        //foo.field5() //you can't call field5 because you can't borrow { !x, .. } which is { Foo::y }
    }
...

borrow except Foo::x

...
    fn field6(&self.{ !x, .. }) {
        
    }
}

field and borrows in impl block

trait Baz {
...

you can declare field in trait block

it needs to be implemented when struct implements the trait

...
    field baz_x: usize;
...

borrow all fields of Self

...
    fn field7(&self.{ .. }) -> usize {
        self.baz_x //declared trait can be used here
    }
...

borrow all fields of Baz

so it's parital borrow

...
    fn field8(&self.{ Bar::{ .. } }) -> usize{
        self.baz_x
    }
...

you can declare borrows in trait block

like in this case, if you don't initialize it it will need to be implemented when implementing trait

...
    borrows f: Self;
...

you can add required borrows to Self::f when implementing Baz::field9

...
    fn field9(&self.{ Self::f }) -> usize;
...
you can borrowed `Self::f` so you can call field9
...
    fn field10(&self.{ bar_x, Self::f }) {
        self.field11();
    }
...
...
    fn field11(&self.{ Self::f }) -> usize;
}

implementing trait with field and borrows

impl Baz for Foo {
...

if Foo already has field with same name this can be accessed like foo.(Baz::field_name)

...
    field baz_x: usize = Self::x;
...

implementing borrows

...
    borrows f = Self::{ y, x }
...

Foo::f has Foo::y so you can access Foo::y

...
    fn field9(&self.{ Self::f }) -> usize {
        self.y //Foo::f has Foo::y so you can access Foo::y
    }
...

you can borrow less than declared in trait

...
    fn field11(&self.{ y }) -> usize {
        self.y
    }
}
fn field13() {
    let foo = Foo {
        x: 42,
        y: 39,
    };
    
    assert_eq!(
        foo.(Baz::bar_x),
        foo.(Foo::bar_x)
    );
    
    assert_eq!(
        foo.(Baz::bar_x),
        foo.bar_x
    );
    
    assert_eq!(
        foo.field8(),
        foo.y
    );
    
    assert_eq!(
        foo.field8(),
        foo.field9()
    );
}

example usage

trait DerefField {
   type FieldType;
   field deref_field: FieldType;
}

impl<T: DerefField> Deref for DerefField {
    type Target = Self::FieldType;
    fn deref(&self { deref_field }) -> &Self::Target {
        &self.deref_field
    }
}

struct Example<T> {
    value: T
}

impl<T> DerefField<T> for Example<T> {
    type FieldType = T;
    field deref_field = self.value;
}
struct LivingData {
    health: usize,
    hunger: usize
}

trait Living {
    field living_data: LivingData;
    
    pub field health = Self::living_data.health;
    pub field hunger = Self::living_data.hunger;
}

struct Cat {
    living_data: LivingData
}

impl Living for Cat {
    field living_data = Cat::living_data;
}

Reference-level explanation

field should work like re-exporting in module level

there can be a field with different ident pointing same field

field in trait object won't work but you can add a function that returns reference of field

&T::{ .... } will work like a type which &T or &mut T or &T::{ ..... } can be implicitally casted into

casting &T or &mut T into &T::{ .... } will borrow every field of it in braces

and won't work if can't borrow it

&T::{ ... } will work same as &T or &mut T in low level like &mut T does like &T

trait fields will work by storing relative position in the memory

Drawbacks

Why should we not do this?

Rationale and alternatives

  • Why is this design the best in the space of possible designs?
  • What other designs have been considered and what is the rationale for not choosing them?
  • What is the impact of not doing this?

Prior art

partial borrow

fields in trait

Unresolved questions

  • What parts of the design do you expect to resolve through the RFC process before this gets merged?
  • What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
  • What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?

Future possibilities

something like partial move will be implemented in future then name of borrows will need to be changed

we won't need to add get() or get_mut() for every field the trait is using and now it will only borrow the exact field

Thanks for this proposal!

The motivation is missing. It currently says what the proposed features can do, but not, why these features are needed.

I think the field keyword could very well be omitted:

trait Foo {
    foo: usize;
}

The second part of the proposal (borrows) seems unnecessarily complex to me. I think a simpler, more familiar syntax would be better, e.g.

struct Foo(bool, i32);

impl Foo {
    // borrows only the first field of Foo
    fn foo(self @ Self(a, _)) -> bool {
        a
    }
}

The borrows items seem unnecessary to me. They add a lot of complexity for very little gain.

Note that this proposal currently proposes two distinct features, so it would probably require 2 RFCs.

@Aloso thanks, I agree that field keyword is unnecessary

struct Foo(bool, i32);

impl Foo {
    // borrows only the first field of Foo
    fn foo(self @ Self(a, _)) -> bool {
        self.bar(); //<-- 
    }
    fn bar(self @ Self(a, _)) -> bool {
        a
    }
}

I saw a similar thing on partial borrow issue, you won't be able to call bar() in foo() in this case

I thougth field will help to explain borrows that's why I put them together

Hmm... does it not look too much like binding a pattern after @?
(e.g. binding both self and a, Example)

Would it perhaps be better instead to spell it like this?

impl Foo {
    // borrows only the first field of Foo
    fn foo(self : Self(a, _)) -> bool {

This seems to overlap with my proposal, which was postponed because it could possibly be implemented as a crate.

Pre-RFC: [Idea] Pointer to Field

RFC: https://github.com/rust-lang/rfcs/pull/2708

Related Crate: GitHub - RustyYato/generic-field-projection: This crate was created to implement the ideas in this RFC #rust-lang/rfcs/2708

1 Like

using pointers will restrict chance of optimization and it's not managed by borrow checker

I'm expecting foo.(Foo::x) (for Foo::x and its export) and foo.x to compile in same way and be managed by borrow checker

The borrow checker has no influence on genrated code at all. It is pure compile-time analysis. In fact before we hit LLVM, references and raw pointers are unified into the same representation, so there is no chance of any differences in optimization differences. edit: references and raw pointers are unified to a similar representation (where references get some extra LLVM annotations for optimization purposes), but this isn't a big deal because as @rpjohnst says below, these raw pointers are transient.

I tried all of your examples, and the final generated code has no difference at all: Compiler Explorer. There is a small difference in the LLVM IR, but that doesn't matter that much: Compiler Explorer.

2 Likes

Careful, this is a big oversimplification! Lifetimes and the borrow checker's results are erased, but there is a very big difference between references and raw pointers when it comes to optimization, and this is basically those types' whole reason to exist! For example see Enable noalias annotations · Issue #54878 · rust-lang/rust · GitHub.

It's possible that a field projection library could be implemented in such a way as to avoid this, because it's mostly in the business of offsets and so it could use raw pointers only transiently, but that's a much narrower distinction.

2 Likes

https://godbolt.org/z/zq95oz except use_add it's usually being optimized and results this (i didn't expected use_offset to work)

example::use_field_test:
        mov     eax, 2
        ret

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.