-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Proposal: Name annotation for tuple types #2870
Comments
Is this the same as structural records? #2584 |
@SimonSapin I don't think they're same.
|
Yes, structural records are anonymous. They’re not The difference seems to be that in your proposal, positions/ordering are still meaningful?
Does this mean that the |
@SimonSapin fn test1() -> (a: i32, b: f64) {
...
}
fn test2() -> (b: i32, a: f64) {
...
}
fn main() {
let x = test1();
let y = test2();
} In |
This implies that fn main() {
let mut x = test1();
x = test2();
} In that case I don’t understand what you meant by:
|
I believe the labels are merely syntactic sugar here and are not part of the type system. |
Yes. It requires no change to existing type system and it just a sugar for compiler and code analysis. |
It breaks down on reassignment with conflicting labels though. What do you envision happens then? let mut foo = (a: 5, b: true);
foo = (x: 7, y: false);
foo.a // ???? |
It means the type of If they're different types, assigning a |
What if we don't allow the syntax I've modified my original proposal. |
I believe this still breaks down on reassignment: fn bar() -> (a: i32, b: bool) { ... }
fn baz() -> (x: i32, y: bool) { ... }
let mut foo = bar();
foo = baz();
foo.a // ???? Unless you can't specify types like this in function return types? That would start to become very inconsistent (also much less useful than structural records). |
This is a contradiction to me. Where else would the names exist but as part of the type? |
I think it'd better fix its name labels to the first assignment, this behavior is also used in C# fn bar() -> (a: i32, b: bool) { ... }
fn baz() -> (x: i32, y: bool) { ... }
let mut foo = bar();
foo = baz();
foo.a // foo.a is i32
foo.b // foo.b is bool
foo.x // compile error
foo.y // compile error |
The names of a field in tuple aren't part of a type, they are only language sugar for better code readability and understanding. It can be achieved completely by compiler and code analyzer without any change to existing type system. Here is a full example: fn bar(v: (a: i32, b: bool)) -> (x: i32, y: bool) {
v.a // ok
v.b //ok
v.u // error
v.v // error
return v;
}
fn main() {
let tup: (u: i32, v: bool) = (5, false);
let mut result = bar(tup);
result.x // ok
result.y // ok
result.a // error
result.b // error
result.u // error
result.v // error
result = tup;
result.x // ok
result.y // ok
result.u // error
result.v // error
} |
That the labels get returned from one function to another like that seems equivalent to "the labels are part of the type" to me. They're still metadata about What I see in this snippet is not an absence of label typing, but implicit coercions between named tuple types that differ only in their names. That raises questions like "does |
@Ixrec |
I believe the way it could work is if the labels are simply entirely ignored during type checking and only ever considered when accessing tuple fields. And then it uses the labels of the initial declaration of the binding. Not sure if that has any other remaining ugly edge cases, but that at least solves the type problematic for the most part. So you could do: let foo: (a: i32, b: bool) = (x: 5, y: false); and it would type check just fine. And foo.a and foo.b are available, not .x or .y. Any reassignments don't change the labels. |
Since that didn't really answer the question, let's try making the "function value" case explicit. What would you want to happen with this code?
|
fn bar(v: (a: i32, b: bool)) -> (x: i32, y: bool) {
v.a // OK
v.b // OK
v.u // NOT OK, v wasn't declared with having u
v.v // NOT OK, v wasn't declared with having v
// .x and .y aren't ok either
return v;
}
fn applyToTuple(
f: fn(v: (fa: i32, fb: bool)) -> (fx: i32, fy: bool), // OK, labels are ignored in type checking
v: (vu: i32, vv: bool)
) -> (g: i32, h: bool) {
f(v) // OK, labels are ignored in type checking
}
fn main() {
let tup: (u: i32, v: bool) = (5, false);
let mut result /* : (g: i32, h: bool) */ = applyToTuple(bar, tup); // type gets inferred from applyToTuple
result.x // NOT OK, result wasn't declared with having x
result.y // NOT OK, result wasn't declared with having y
result.a // NOT OK, result wasn't declared with having a
result.b // NOT OK, result wasn't declared with having b
result.g // OK
result.h // OK
result.u // NOT OK, result wasn't declared with having u
result.v // NOT OK, result wasn't declared with having v
result = tup;
result.x // NOT OK, result wasn't declared with having x
result.y // NOT OK, result wasn't declared with having y
result.a // NOT OK, result wasn't declared with having a
result.b // NOT OK, result wasn't declared with having b
result.g // OK
result.h // OK
result.u // NOT OK, result wasn't declared with having u
result.v // NOT OK, result wasn't declared with having v
} Another thing is how they affect type inference in cases like this: let foo: (_, x: bool) = (a: 5, b: true);
foo.a // OK, foo got inferred as (a: i32, x: bool) but not let foo: (i32, bool) = (a: 5, b: true);
foo.a // NOT OK, foo explicitly has no label on the first tuple value. |
@CryZe Thanks. It's exactly what I meant. |
If there's no more problem with this proposal, I will send a RFC PR soon. |
Some more interesting cases:
In C#, tuple element names are part of the type (at least as far as the C# compiler is concerned; the runtime types are different -- essentially C# has "tuple name erasure"). |
My opinion is: name resolving should based on how the tuple type declared, so names of the return value can be inferred.
This is okay but I think the compiler should produce a warning for name losing or name changing. |
I do think rustc should eventually support metadata within its type system, but for optional external analysis tools that do formal verification, refinement types, etc., not for syntactic sugar. If I understand this, there are numerous weaknesses compared with structural records, like type system strangeness and warnings where errors belong, but no real advantages over structural records. It just sweeps the structural record design space under the rug. We've discussed structural records extensively in #2584 and elsewhere. We've even discussed almost exactly this "order matters" approach briefly I think. Although #2584 remains open, we've learned structural records interact with far more important type system extensions, like delegation and fields-in-traits. I presume those remain blocked on at least one "in-progress designs and efforts", ala cost generics, specialization, async, chalk, etc. We also noticed tricks that improve upon structural records for many applications:
|
I think it would be better that way let unamedstruct: { count: i32, price: f64, type: u8 } = { count: 500, price: 6.4, type: 1 }; or simply let unamedstruct = { count: 500, price: 6.4, type: 1 }; nothing to do with tuples, starting with tuples having no named elements, structs yes |
@cindRoberta I think this is the purpose of RFC #2102. |
Julia uses it with NamedTuple |
Background
Here're ways to access tuple element in Rust for now:
The first approach is a deconstruct process, which deconstruct the tuple
x
intoa
,b
andc
.The second approach is to access elements in the tuple
x
directly.However, when we use a tuple, it's hard to know meaning of its elements without comments or documents, and things like
.0
are hard to distinguish if you lack acknowledge of a tuple.Proposal
I proposed name annotation for tuple types:
And then we can access elements in the tuple
x
in this way:It's more intuitive and convenient.
Note that
count
,price
andtype
are just name annotations for the tuple type, and they don't change the actual type(i32, f64, u8)
.x.count
will be compiled tox.0
.Name resolving
Names are resolved as ways they're declared.
The text was updated successfully, but these errors were encountered: