Unify structs, tuples and funciton calls


#1

I’ve just came up with an idea!

Sorry, if this is not new idea, I know that there were serveral discussions on the subject, and I briefly read them.

Idea

I propose unification of syntax of

  • structs
  • tuple structs
  • tuples
  • funcitons
  • introduction of function named arguments
  • introduction of tuples with named fields
  • introduction of function parameter default values

Current state

Currently Rust has five objects that has value arguments: structs, tuple structs, tuples, functions (and closures).

Syntax of construction/call of these things:

struct Foo { a: A, b: B } // struct
struct Baz(A, B)          // tuple struct
(A, B)                    // tuple
fn baz(a: A, b: B) -> ... // function

Syntax of calling these things:

Foo { a: a, b: b }
Bar(a, b)
(a, b)
baz(a, b)

And, these things have member access operators:

foo.a
bar.0
tuple.0
(not appilcable to function calls)

So, my idea is this

  1. No curly braces in structs
  2. all struct fields have indexes, some fields have names
  3. so tuple structs are just structs with all fields without names
  4. tuples are anonymous structs with the same rules
  5. function call arguments is the same thing (to some degree) as tuples

So, objects that I described before, now written as:

struct Foo(a: A, b: B) // like function declaration
// and the rest as before
struct Bar(A, B)
(A, B)
fn baz(a: A, b: B) -> ...

and invocation syntax (using .a = instead of colon):

(I prefer .a = to => syntax, because of similarity to C and to field access operation, but that’s not important right now).

Foo(.a = a, .b = b)
Bar(a, b)
(a, b)
baz(a, b)

However, the best thing is that this syntax allows intermixing of named and positional arguments!

Foo(a, .b = b)      // Foo(.a = a, .b = b)
Bar(.0 = a, .1 = b) // Bar(a, b)
(a, .1 = b)         // (a, b)
baz(a, .b = b)      // the same as baz(a, b)

That’s it!

And also, note side effects:

Struct delcarations can mix positional and named fields

struct Foo(A, B, c: C, d: D, e: E, f: F)

Tuples can have named fields

fn get_host_and_port() -> (host: String, port: u16) { ... }

let host = get_host_and_port().host; // or .0

Default values

Probably this change should come together with function parameter default values (and because structs and functions are unified, struct may have default field values too):

fn connect_to_http_server(host: String, port: u16 = 80) { ... }
struct HttpConnection(host: String, port: u16 = 80)

connect_to_http_server(host) // use default port
HttpConnection(host)         // use default port, do not specify field name

Syntax ambiguity

This change resolves minor syntax ambiguity when parsing if with struct. This code is not allowed now:

if Foo { a: 10 } == foo { ... }

This will be allowed:

if Foo(.a = 10) == foo { ... }

Last words

This is not a final proposal, there are thousands of things to be thought about. Please let me know! Especially if I missed something.


#2

Hy, I have nothing against you proposal but there are some (potential) problems I want to point out.

  • accessing struct fields over indexes is a compability harzad for library’s e.g. if you add a new field at the beginning of a struct all indexes will change creating a lot of refactoring need for all parts using that struct. Even worse if the types happen to be the same there will be no compiler error, leading to possible logic bugs.

  • the compiler reorders the order of fields (for better alignment witch can reduce the total size of a struct). That can but should not lead to problems when implementing the proposaly.

  • fundamental changes of the syntax at alpha level. This is not really a technical problem but changing a syntax aspect witch maks every rust file syntactic incorrect at alpha level might not be the best idea

  • I an not sure about this but I think I have read that tuples have some quite different implementation intern. Even so this should not be a problem for the proposal or?


#3

Hi, @naicode,

accessing struct fields over indexes is a compability harzad…

  1. same issue exists today for tuples and tuple structs, it is not a real problem
  2. if struct has names, indexes should not be used for directly access fields
  3. however, field indexes are useful in pattern matching, e. g.
struct SocketAddr(ip: IpAddr, port: u16);

match socketAddrOpt {
    // fields are unpacked by index
    // currently similar syntax is allowed, but fields are unpacked by name
    Some(SocketAddr(ip, port)) => ...
}

the compiler reorders the order of fields

I’m talking about logical field numbering. It is unrelated to struct representation in memory.

fundamental changes of the syntax at alpha level. This is not really a technical problem but changing a syntax aspect witch maks every rust file syntactic incorrect at alpha level might not be the best idea

User code needs only one change: replace curly braces with parentheses in structs. I think it can be made mechanically with simple script.

And rust compiler should accept both new and old syntax for some time.

fundamental changes of the syntax at alpha level

I’m not sure if it’s too late.

I an not sure about this but I think I have read that tuples have some quite different implementation intern. Even so this should not be a problem for the proposal or?

I’m not sure. I don’t see any hidden catches for now.


#4

See RFC 841 and RFC 866.

TL;DR: No, it is already too late for a large scale breaking change that only makes code subjectively a bit nicer.


#5

It would have been nice having such unification in the language.


#6

No, it is already too late for a large scale breaking change

Syntax before this change can be available at 1.0 but deprecated.

that only makes code subjectively a bit nicer.

No, it not just nicer. This change:

  • resolves ambiguity when parsing if/struct
  • resolves readability issues with type ascription
  • makes language simpler because of uniformity
  • opens possibility to have function call named parameters and parameter default values, and struct field default values, with the same syntax

Thanks for the links BTW, they describe things close to what I need.


#7

How does the destructuring of function parameters fit into the picture?

fn func(&mut a: &mut u8, (b, c): (u8, u8), [d, e..]: [u8; 3]) {
}

#8

How does the destructuring of function parameters fit into the picture?

I’m not a big fan of destructuring in function parameters (because it makes function signatures less readable), but it could be written like this:

fn func(&mut a: &mut u8, (b, c): (u8, u8), [d, e..]: [u8; 3]) { ... }

struct Foo(usize, u32);
fn func(&mut Foo(a, b): &mut Foo) { ... }

struct Bar(a: usize, b: u32) { ... }
fn func(&Bar(.a, .b): &Bar) { ... } // unpack by name
fn func(&Bar(x, y): &Bar) { ... } // unpack by index

// or probably even
fn func(&mut (a, b): &mut Foo) { .... }
fn func(&(.a, .b): &Bar) { ... } // unpack by name
fn func(&(x, y): &Bar) { ... }

#9

Yeah, me too. I’m asking only because they exist and have to be dealt with somehow.

Btw, patterns also support mut and tuples/structs don’t support field-by-field mutability. That’s one more deviation of function parameters from the “tuple model”.


#10

One problem of introducing named arguments to functions is that you make the names of the arguments part of the signature. Changing a name of an arguments becomes a backwards-incompatible change.


#11

One problem of introducing named arguments to functions is that you make the names of the arguments part of the signature. Changing a name of an arguments becomes a backwards-incompatible change.

I think it is not a problem. It is proven by Python: all function arguments in Python are named, and it did not cause any noticeable troubles to my knowledge.

Another example is Objective C where parameter names are (kind of) always named:

NSString * fileContents = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

However, I consider an alternative, to allow only parameters with default values to be specified by name.