Unify structs, tuples and funciton calls

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.

4 Likes

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?

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.

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 Likes

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

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.

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]) {
}

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) { ... }

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".

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.

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.

1 Like

Although this thread is already quite old, so I asked myself the same or similar question, why not from the beginning such a simplification was made.

struct user {
...
}

let user1 = User {...};

and why not like C:

let user1: User = {...};

anyway the following syntax is correct: let user1: User = User {...};

Alternatively, when defining objects as a structure one could always have required the specification of the composite data type. And on top of that, in this case, tuple-like structures would not look like functions, which I suspect is intentional? Maybe to be able to use tuple structures like functions in some places (Closure)? In this proposal here, then basically all structures would appear like functions (and possibly somehow be used). Forgive me, but I’m pretty C-biased, which is why it still seems very strange to me that the string name must necessarily be in front of the curly brackets or parentheses.

In any case, I support this standardization, and also think that round brackets are more appropriate in order to make structure definitions clearer from statement blocks or other expressions. Tupels are, so to speak, only non-specific structures. I think that this simplification and simultaneous flexibility in dealing with structures would not only make Rust more intuitive, but also lead to other interesting simplifications that make programming easier. But I doubt that such syntax changes are still enforceable, or could convince the majority of responsible persons, although such changes, for example, in a Rust 2.0 are conceivable. And a corresponding tool could automatically make the work of revision existing source code, so no one would have to seriously start to adjust the standard library by hand. However, it could be problematic if, while retaining the existing syntax - that is, the structure name in front of the parenthesized block with the values ​​- even ordinary structures suddenly appear as functions, so that naming conflicts with actual functions occur here massively.


Optional parameters have always been something I often missed at C to control elegant functions / algorithms. It is a pity that in Rust this is only possible through a “builder”. It would be something much nicer:

fn add_somewhat (a: i32, b: i32 = 0, c: i32, d: i32 = 0) -> i32 {a + b + c + d}

How the interface is actually used, decides the user and is also of no concern:

let x = add_somewhat (1, 2, b: 3); // (a, c, b)

let x = add_somewhat (b: 3, 1, 2); // (b, a, c)

let x = add_somewhat (1, c: 5, b: 3, d: 2); // this time argument c was passed over the name and not positional: (a, c, b, d)

As a rule could be: Optional parameters may only be passed over the name, while the remaining mandatory arguments can be either positional or also via the name. The programmer of a function ultimately does not have to worry about how its function is called by others. In similar threads where optional parameters were concerned, I had read a variety of suggestions; where I consider the variant I described as the simplest, without having to introduce any additional brackets, keywords or the like. Anyone who rejects optional parameters or labeled arguments can pass their values to a function as usual (except for optional parameters)

It's not, we've got the "rest" syntax for structs as well. (It's a good idea to make e.g. a Default-implementing struct for your function if it starts accepting more then a few arguments anyway.)


To the point, Swift has a record of "unifying" function arguments and tuples. It didn't work out well. In fact, it turned out to be so bad that they removed it from Swift 4. Yes, in theory it's appealing. In practice, however, it results in a bunch of issues, inconveniences and ambiguities in other parts of the language, so ultimately it isn't really worth it.

1 Like

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