Skip to content
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

Add syntax to partially destructure self in method signatures. #83

Closed
wants to merge 1 commit into from
Closed

Add syntax to partially destructure self in method signatures. #83

wants to merge 1 commit into from

Conversation

mahkoh
Copy link
Contributor

@mahkoh mahkoh commented May 20, 2014

No description provided.

@mahkoh
Copy link
Contributor Author

mahkoh commented May 21, 2014

I'm personally more a fan of alternative 1 but there seems to be considerable opposition to making the borrow checker work across methods. This can lead to surprising errors since it is not clear at all which fields I'm allowed to use in a method. In this example, using self.a in g would suddenly make the compilation fail, even though the function signature doesn't indicate that I'm not allowed to do this. I think this might be acceptable as long as it only happens within the same impl block.

The following alternative is a variation of the syntax proposed here and closer to alternative 1 but removes the compiler magic.

Consider the following struct and functions:

struct X {
    a: T,
    b: U,
    c: V,
}

fn f(x: &mut X) {
    g(x, &mut x.a);
}

fn g(x: &mut X, a: &mut T) {
    /* function that doesn't use x.a */
}

This doesn't work for the obvious reasons.
What is missing is a way to indicate in g's function signature that g doesn't use x.a.

fn g(x: &mut X{b, c}, a: &mut T) {
    /* function that doesn't use x.a */
}

This syntax would indicate that g only uses the fields b and c. Note that this is not destructuring the object. Inside g, the fields are accessed the usual way: x.b and x.c. The only difference between this and the signature without the {b, c} is that one isn't allowed to access x.a inside g.

Some examples:

fn f(x: &mut X) {
    g(x, &mut x.a);
}

fn g(x: &mut X{b, c}, a: &mut T) {
    h(x, a, &mut x.b);
}

fn h(x: &mut X{c}, a: &mut T, b: &mut U) {
    /* ... */
}
// impl X
fn f(&mut self) {
    self.g(&mut self.a);
}

fn g(&mut self{b, c}, a: &mut A) {
    /* ... */
}

As you can see, &mut X{b, c} means that b and c can be accessed mutably. To give more fine grained control over this, one can drop the initial mut and write it like this:

fn f(x: &mut X) {
    g(x, &mut x.a);
}

fn g(x: &X{b, mut c}, a: &mut A) {
    /* b is immutable */
}

I think this approach contains all the good parts of the current proposal while also extending it to arbitrary objects (not self).

The only downside is that the syntax can get quite messy. However, consider that without this syntax you have to manually destructure the objects and pass the arguments individually which makes the function signatures and calls even longer.

Edit: One could go so far as to add a new keyword to cover the most common cases:

fn g(x: &mut X{not a}, a: &mut T) {
    /* function that doesn't use x.a */
}

@lilyball
Copy link
Contributor

I feel like this proposal will conflict quite severely with UFCS. Which is to say, self cannot have special rules if we have UFCS because UFCS won't be able to apply those rules.

@thestinger
Copy link

Up to this point, patterns have been an internal implementation detail rather than part of the external API. Using this to perform partial borrows creates a backwards compatibility hazard and takes a step away from treating methods as normal functions.

@mahkoh
Copy link
Contributor Author

mahkoh commented May 21, 2014

@kballard: @thestinger mentioned on IRC that you were trying to move away from treating methods as a special case but I hadn't heard of UFCS before. In this case you can forget about the original RFC.

This leaves us with the "alternative" I proposed in the comment above which works for all function calls and not just methods.

@thestinger: Are you against these partial borrows in general or just against the destructuring version I proposed in the original RFC?

@lilyball
Copy link
Contributor

I think that if you want to propose something like this for arbitrary methods, what you really want to do is come up with some definition for a "struct slice". By that I mean some pseudo-type that means "fields x, y, z from struct Foo". Then you could use this to declare an argument as being of the type of that struct slice. Given that, whenever a struct is partially-moved or partially-borrowed, the compiler could use that to define the maximal valid struct slice (or maximally-borrowable struct slice, for taking &mut references). With that, you'd then be able to call a function that takes any sub-struct slice of that maximally valid slice.

Basically, I'm suggesting a way to make the type system aware of what you're trying to do, instead of having it be crazy pattern shenanigans.

My proposed syntax for a "struct slice" would be Foo[x,y,z]. Although this only works for structs with named fields, perhaps a tuple struct would be Foo[0,2,3] (or perhaps tuple structs just aren't compatible with this, since you need to destructure them anyway to gain access to their fields).

That said, I feel like this is a post-1.0 sort of thing.

@mahkoh
Copy link
Contributor Author

mahkoh commented May 21, 2014

Basically, I'm suggesting a way to make the type system aware of what you're trying to do, instead of having it be crazy pattern shenanigans.

The X{b, c} syntax was just to illustrate the concept. What you're calling "struct slice" seems to be what I had in mind (minus borrowing some fields mutable and some fields immutable.)

@lilyball
Copy link
Contributor

Well, what you're proposing seems to be just special destructuring stuff that the compiler uses to inform borrowck about whether it's safe to pass the value to the function.

What I'm suggesting is that it be formalized into the type system properly. Although thinking again on my "struct slice" idea, it doesn't handle the case where a field of a struct is itself only partially-moved. Fixing that issue will probably make this a lot more complicated.

@huonw
Copy link
Member

huonw commented May 23, 2014

"struct slicing" seems similar to "datasort refinements": http://smallcultfollowing.com/babysteps/blog/2012/08/24/datasort-refinements/ and rust-lang/rust#1679

@arcto
Copy link

arcto commented May 28, 2014

Would a struct slice be a "derived type" in that its definition is based on another type, limiting access to its members but remembering the size of the original struct?

Another view on the semantics of slicing structs could be in terms of set theory. Each struct slice is a subset of accessible members.

In some ways this would be similar to traits.

@nikomatsakis
Copy link
Contributor

I think that this more-or-less falls out of the current plans for method dispatch and UFCS. Basically the &self, self, and &mut self syntax remains as sugar, but it is always legal to write foo: Self etc as you like. That means you can write fn method((x, y): (uint8, uint8)) or something like that and have it work. Therefore, going to close as being superceded by the method dispatch plans from RFC #48.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants