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

proposal: anonymous struct literals #35304

Open
rogpeppe opened this issue Nov 1, 2019 · 44 comments
Open

proposal: anonymous struct literals #35304

rogpeppe opened this issue Nov 1, 2019 · 44 comments
Labels
dotdotdot ... LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@rogpeppe
Copy link
Contributor

rogpeppe commented Nov 1, 2019

Currently it is not possible to write a struct literal without mentioning its type (with the exception of struct literals in map and slice composite literals). This can make it extraordinarily hard to write literals for some types. As an extreme example, consider writing a literal initializer for the sarama.Config type. All the unnamed struct types need to be written out explicitly, leading to a highly redundant expression 100s of lines long, and also fragile because any fields that are added will cause breakage.

I propose that Go adds a way of writing an anonymous struct literal - a struct literal that has no explicit type - by using the blank identifier (_) as a type name in a composite literal.

For example:

_{
	Name: "Bob",
	Age: 12,
}

Unlike other composite literals, it would require all members to be qualified with field names (no non-keyed elements).

Any field values in the literal that are const will remain const. If all fields in the literal are const, the entire literal is also considered considered const too, forming a struct constant.

For example:

const Bob = _{
	Name: "Bob",
	Age: 12,
}
const BobAge = Bob.Age

Like other constants, an untyped struct constant has a default type, which is the anonymous struct type with all fields converted to their default type.

So, for example, the default type of _{} is struct{}; the default type of _{Name: "Bob", Age: 12} is struct{Name string, Age int}.

An anonymous struct literal can be assigned to any struct type that contains a superset of the fields in the literal. Any const fields are treated with the usual const assignment rules. Fields that are not mentioned will be zero.

So, for example:

type Person struct {
	Name string
	Age uint32
}
var alice Person = _{
	Name: "Alice",
	Age: 32,
}

Unlike other struct literals, it would not be possible to use the & operator, so:

&_{Name: "Bob"}

would be invalid. There might be an argument for allowing assignment of an anonymous struct literal to a pointer type, so:

var P *Person = _{Name: "Bob"}

would be the same as:

var P *Person
var P1 Person = _{Name: "Bob"}
P = &P1

This would fit with the way that literal elements in map and slice literals work.

@rogpeppe rogpeppe added the v2 A language change or incompatible library change label Nov 1, 2019
@gopherbot gopherbot added this to the Proposal milestone Nov 1, 2019
@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 1, 2019

Discussion

Related proposals

Issue #12854 proposes type-inference for composite literals, but it's more general (including support for maps and slices) and doesn't propose allowing const structs.

That proposal is also somewhat more restrictive - it doesn't allow the untyped composite literals to be used in places where the type being assigned to isn't clear.

By contract, this proposal always allows such a literal to be formed, and applies the same kind of rules for other Go values and constants.

In #12854, the fact that the type is inferred from the destination can make the resulting code hard to read. If I see a statement like:

foo({x: y})

I can only tell if x is a field name or an expression by looking at the type of foo. In foo(_{x: y}) in this proposal, x is always a field name and never an expression.

Issue #9859 is also relevant. If we allow direct reference to embedded fields in struct literals, perhaps we should make assignment from untyped struct literals work for that too, allowing this, for example:

type T struct {
	X int
}
type S struct {
	T
	Y int
}
var s S
s = _{X: 10, Y: 20}

Zero value

All types in Go other than struct types and arrays have short representations for their zero value. Pointers, maps, slices and channels have nil, strings have "" and numbers have 0. This proposal implies a universal form for structs too: _{}.

This makes it less verbose to return from struct-returning functions:

func Foo() (somepkg.SomeStructType, error) {
	if err := doSomething(); err != nil {
		return _{}, err
	}
	return somepkg.SomeStructType{
		// etc
	}, nil
}

It also makes it slightly less verbose to form an empty struct{} literal:

m := make(map[string]struct{})
m["a"] = _{}

Struct constants

There are places where it would be nice to be able to have struct constants. For example, image.ZP is a variable, and deprecated mostly, I believe, because using it is inefficient (the compiler is unable to prove that it doesn't change).

const ZP = Point{}

wouldn't have those disadvantages.

Struct parameters

It's common for functions to accept a struct parameter argument containing an extensible set of parameters. Often, this is defined as a pointer type because it's inconvenient to write the struct literal, where it would be more natural to pass it by value.

With anonymous struct literals, this idiom becomes less weighty. Instead of:

x, err := foo.New(foo.Params{})

you'd be able to write:

x, err := foo.New(_{})

Anonymous map/slice literals

This proposal makes a deliberate choice that an anonymous composite literal represents a struct literal, not a map, slice or array literal. The main reason for doing this is that it means that the compiler can know that the keys are field names, not arbitrary expressions. Allowing an anonymous composite literal to represent other kinds of types would significantly complicate the spec.

Compatibility

The syntax is compatible with existing Go parsers, so current gofmt formatting wouldn't need to change. As _ is always an error to use as a type name in a literal, there shouldn't be any problem with breaking existing code.

Updates would be needed to the go/types packages and other associated tooling.

@networkimprov
Copy link

Apologies if it's a bikeshed, but maybe struct{_} {...}?

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 1, 2019

Apologies if it's a bikeshed, but maybe struct{_} {...}?

That has the advantage of being more obviously a struct literal, but it's more verbose - the syntactic light weight of the original proposal seems to me to be a significant point in its favour.

@tandr
Copy link

tandr commented Nov 1, 2019

If (and that is a big IF) I understand it correctly, this proposal is not so much about anonymous structs, but more of a const structs - at least all examples are const

(below is Thinking Out Loud, and breaking old visual conformity)

if so... why don't we entertain the idea to use word const instead of "_" ?
This way you know

  1. you cannot use it as r-value (using C++'s terms), but only as a left side.
  2. you cannot take a direct address of it. So if you do, it will introduce a copy and then address
  3. you don't exactly have a type until you assign it to something, sort of like const works on a simple consts
t := const { Name: "Bob", Age: 12 }

would need to be made illegal, since t would have no type that can be derived. Or... (to go with anonymous types), it would become a

var t struct {Name string, Age int} 
t = const { Name: "Bob", Age: 12 }

automatically. (Might not that be that bad actually)

Retyping all the examples above to see how it "feels"

const Bob = const {
	Name: "Bob",
	Age: 12,
}
const BobAge = Bob.Age
type Person struct {
	Name string
	Age uint32
}
var alice Person = const {
	Name: "Alice",
	Age: 32,
}

(although, in this example, I would drop the const altogether, more of what #12854 does. Different meaning though with and without const - one is type elided, another is copied of const struct into new Person.)

Last one is a bit of a gotcha

var P *Person = const {Name: "Bob"}

would be the same as:

var P *Person
var P1 Person = const {Name: "Bob"}
P = &P1

but... feels breaking the rule of const having no address. But then again, existing example would be an initializing array of pointers with just (const) structs.

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 1, 2019 via email

@urandom
Copy link

urandom commented Nov 3, 2019

The problem is that currently you can write something like this

var foo = map[string]struct{
   Foo int
}{
    "1": {Foo: 1},
    "2": {Foo: 2},
}

which is why #12854 wants to extend the places where that is allowed.

Whereas this would introduce a slightly different syntax, which leads to the following problem:
Either change the valid syntax mentioned above to be in the form of _{Foo: 1}, or have 2 different syntaxes for the same thing, used in different places.

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 3, 2019

Either change the valid syntax mentioned above to be in the form of _{Foo: 1}, or have 2 different syntaxes for the same thing, used in different places.

In this proposal, this would be valid too, because the _{...} form is valid whereever a struct-valued expression is valid:

var foo = map[string]struct{
   Foo int
}{
    "1": _{Foo: 1},
    "2": _{Foo: 2},
}

The {...} form without the _ qualifier would only be allowed where it is now - it is more general than the _{...} literal (it allows positional members as well as keyed members, and map and slice literals too) and if it's allowed in any expression, it introduces some syntactic ambiguity too (does a { at the start of a statement mean the start of a composite literal or of a braced block?)

To recap: the _{...} form is allowed in any expression, but always implies a struct literal. The {...} form may only be used inside composite literals but may be used for slices, maps and non-keyed struct literals too.

@urandom
Copy link

urandom commented Nov 4, 2019

Not sure if that's better or not. You'd have 2 different syntaxes for the same thing now. In some places, both can be used, but in others, only 1 can be used. And the users are either gonna have to know the spec pretty good, or suffer through the learning curve of whether to use which syntax.

IMHO, i would prefer if #12854 is implemented wherever its unambiguous. I'm also wondering whether

I can only tell if x is a field name or an expression by looking at the type of foo. In foo(_{x: y}) in this proposal, x is always a field name and never an expression.

is really a big problem. My guess would be that structs are a lot more prevalent as argument types than maps, so if you really care about the type, a guess that its a struct would more than likely be correct. For a casual reader, would it really matter if the argument was a struct or a map anyway? And finally, for a writer, you'd need to know the type beforehand, otherwise can you really say you know what you're writing?

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 4, 2019

Not sure if that's better or not. You'd have 2 different syntaxes for the same thing now. In some places, both can be used, but in others, only 1 can be used. And the users are either gonna have to know the spec pretty good, or suffer through the learning curve of whether to use which syntax.

I think it would probably be fine if gofmt -s (or maybe even just gofmt) removed any redundant _ prefixes inside struct literals. That might be enough.

My guess would be that structs are a lot more prevalent as argument types than maps, so if you really care about the type, a guess that its a struct would more than likely be correct. For a casual reader, would it really matter if the argument was a struct or a map anyway?

I think it does matter. There's a big difference between {name: "hello"} when name is an expression (you'll need to know what it's referring to) and when it's a field (it's really just a field name with no extra cognitive overhead).

And finally, for a writer, you'd need to know the type beforehand, otherwise can you really say you know what you're writing?

That's a good question. In a sense, the struct literals are somewhat analogous to method definitions in that respect. If you write a method definition, there are many possible interfaces it could satisfy, so can you really know what you're writing? With this proposal, it's possible to write a struct constant like this:

const One = _{N: 1}

which is assignable to any struct type that has a numeric field named N. I'm not sure how useful this is in practice, but it falls out naturally from giving untyped literals a first class place in the language alongside other kinds of untyped expression. Personally, I think this is preferable to restricting the contexts in which this kind of literal can be used as #12854 does, something which itself has quite a learning curve and goes against the way that most of the type system works.

@neild
Copy link
Contributor

neild commented Nov 4, 2019

This is two proposals. One is an adjustment to composite literals to permit replacing the name of a struct type with _. The other introduces "struct constants". These two changes are orthogonal--we could have either without the other--and can be considered separately.

Constant structs is a subset of #6386. I don't have anything to add to the discussion on that issue, so the rest of my comments focus on the proposed composite literal syntax change.

Currently it is not possible to write a struct literal without mentioning its type.

This isn't actually true--the type of a composite literal (struct or otherwise) may often be elided when nested within another composite literal.

type T struct{ V int }

var _ = map[T]T{{V: 0}: {V: 1}} // type T elided in map key and value
var _ = []T{{V: 0}}             // type T elided in slice
var _ = []T{{V: 0}}             // type T elided in slice
var _ = [...]T{{V: 0}}          // type T elided in array

The only time the type of a nested composite literal may not be elided is when the containing type is a struct. Permitting elision in this case as well is proposal #21496, and would address the example of sarama.Config. I think we should do this. I find it odd that elision is permitted in the cases above, but not this one:

var _ = struct{K T}{K: {V: 0}} // error: missing type in composite literal

Issue #12854 proposes type-inference for composite literals, but it's more general (including support for maps and slices) and doesn't propose allowing const structs.

It's not quite accurate to say that #12854 proposes type-inference for composite literals. We already permit eliding the type of composite literals in many circumstances; #12854 essentially proposes extending those circumstances. It does this by suggesting that a composite literal without an explicitly specified type be assignable to a compatible variable. The type is taken from the variable, not inferred.

@neild
Copy link
Contributor

neild commented Nov 4, 2019

This proposal makes a deliberate choice that an anonymous composite literal represents a struct literal, not a map, slice or array literal. The main reason for doing this is that it means that the parser can know that the keys are field names, not arbitrary expressions.

The parser already needs to deal with cases where whether a key is a field name or an expression is only knowable after type resolution.

var key = "key"
var _ = T{key: "value"} // Is `key` a field name, or an expression?

The interpretation of key depends on the type of T, which may of course be defined in a different file or package:

type T map[string]string
type T struct{key string}

Limiting this proposal to structs doesn't simplify the parser's job at all.

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 4, 2019

This is two proposals. One is an adjustment to composite literals to permit replacing the name of a struct type with _. The other introduces "struct constants". These two changes are orthogonal--we could have either without the other--and can be considered separately.

That's a reasonable characterisation, except that we're not just talking about constants - it proposes untyped struct literals, which are a different thing. An untyped struct literal is not necessarily constant, although it can be - it's constant iff all its fields are constant.

Currently it is not possible to write a struct literal without mentioning its type.
This isn't actually true--the type of a composite literal (struct or otherwise) may often be elided when nested within another composite literal.

Yup, that's true, thanks. I've adjusted the proposal text accordingly.

I find it odd that elision is permitted in the cases above, but not this one:

var _ = struct{K T}{K: {V: 0}} // error: missing type in composite literal

Personally, I don't find it that odd, because in a large struct, there can be significant cognitive overhead in understanding complex composite literals, particularly when literals omit keys.

For example, in the statement below, there's no clue as to whether the K value is a slice or a struct. When the

var _ = foo.Bar{K: {0, 1, 2}}

I think it's instructive to go through the CLs from the time we actually did this inside the Go source tree.

ISTM that most of the "unreadable" review comments came from situations where the literals were slice literals or struct literals with positional arguments.

The parser already needs to deal with cases where whether a key is a field name or an expression is only knowable after type resolution.

Yes, that's true; I've adjusted the text to say "compiler" there. If you don't know if it's map or a slice or a struct literal, you can't really have the concept of an untyped literal in the same way because the meaning of the key syntax can vary according to how it's used. So the language can't (for example) support assignment of an untyped struct literal to an interface type, something that I suspect might turn out to be quite useful; for example:

someTemplate.Execute(os.Stdout, _{
	Name: "hello",
	Age: 123,
})

@neild
Copy link
Contributor

neild commented Nov 4, 2019

Personally, I don't find it that odd, because in a large struct, there can be significant cognitive overhead in understanding complex composite literals, particularly when literals omit keys.

To make sure we're talking about the same thing: Right now, you're allowed to elide the type from a nested composite literal when the containing type is a map, slice, or array. You aren't allowed to elide the type when the containing type is a struct. I think this is an odd inconsistency.

I don't find arguments about cognitive overhead or readability convincing. You can write utterly unreadable composite literals right now. To use your own example, this is perfectly valid Go code right now:

var _ = foo.Bar{K: {0, 1, 2}}

You can always write unreadable code. The standard to judge language syntax on is whether it can be used to write readable code, and whether it encourages unreadable code. To go back to your example of samsara.Config, this does not seem unreadable to me:

conf := samsara.Config{
  Net: {
    MaxOpenRequests: 10,
  }
}

Yes, that's true; I've adjusted the text to say "compiler" there.

Parser or compiler, the current implementation already needs to deal with the fact that an identifier appearing as a key in a composite literal can only be determined to be a field name or an expression after type resolution has been performed. Limiting the _{ ... } composite literal syntax to structs doesn't simplify the compiler because it still needs to handle the existing cases. I do not believe the assertion that "allowing an anonymous composite literal to represent other kinds of types would significantly complicate the spec" is correct; the complexity is already there today.

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 4, 2019

To use your own example, this is perfectly valid Go code right now:

var _ = foo.Bar{K: {0, 1, 2}}

Yes, and that example is just about OK because, firstly it's clear that foo.Bar must be a slice or a map, and you look at its definition and find out its key and element type, and thence what K and {0, 1, 2} signify. When you get to multiple levels and struct fields with heterogeneous types, that quickly adds a lot of cognitive overhead, which is why I don't support #12854.

To go back to your example of samsara.Config, this does not seem unreadable to me:

conf := samsara.Config{
  Net: {
   MaxOpenRequests: 10,
 }
}

I agree, which is why this proposal would allow that (assuming a _ prefix in front of the Net value).

Limiting the _{ ... } composite literal syntax to structs doesn't simplify the compiler because it still needs to handle the existing cases.

I actually said that it would simplify the spec, not necessarily the compiler. I was talking about a spec allows untyped composite literals, not just type-elided ones, as this proposal does.

If an untyped composite literal can represent a map or a struct, what does it mean if I assign one to an interface{}?

@neild
Copy link
Contributor

neild commented Nov 4, 2019

When you get to multiple levels and struct fields with heterogeneous types, that quickly adds a lot of cognitive overhead, which is why I don't support #12854.

I'm confused by which cases you see as involving cognitive overhead, and which ones as not doing so. The presence or absence of an additional _ prefix doesn't seem to me to have a great deal of influence on the comprehensibility of the samsara.Config example.

Do you see #21496 as introducing too much cognitive overhead? That proposal seems to address your motivating example for this proposal (the difficulty of writing initializers for some types) with a very small spec change.

If an untyped composite literal can represent a map or a struct, what does it mean if I assign one to an interface{}?

You can't, same as you can't assign an untyped nil to an interface{}. An untyped composite literal does not have a default type.

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 5, 2019

Do you see #21496 as introducing too much cognitive overhead?

Yes I do. I think that allowing elision of type specifiers for all kinds of type of field is going a bit too far. I'm hoping that my proposal here is a reasonable half way house in this respect: you can always make a keyed struct literal without specifying the type, but not other types.

An untyped composite literal does not have a default type

It does in this proposal :-)

@urandom
Copy link

urandom commented Nov 5, 2019

In terms of cognitive overhead, i really don't see how adding an extra _ is that much better, tbh. Lets take the following examples:

foo := Config{
    Bar: {
      Baz: {"alpha", "beta"},
   },
}

foo = Config{
   Bar: _{
      Baz: {"alpha", "beta"},
   },
}

Now, it is in fact clear that the value of Bar is a struct. However, that in itself isn't all that useful information. Just knowing that something is a struct, is like knowing that something is a box. You still don't know what's inside of it. And yes, in the first example Baz could be an identifier, but there's a really really good chance that it's not. I personally won't even bother checking that if I see it in an unknown code block.

Adding a _ is an optimization, however its one that I don't think is necessary due in part to the proliferation of structs over maps.

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 5, 2019

Now, it is in fact clear that the value of Bar is a struct.

It might be in this small piece of code, but I don't think that's always true when exploring large foreign code bases. I've been bitten by this assumption before.

What about the value of Baz ? Is that a struct or not?

What about code like this (from a real example from when this was tried in the stdlib)?

stmt := &Stmt{
    css:   {{ci, si}},
}

Adding a _ is an optimization, however its one that I don't think is necessary due in part to the proliferation of structs over maps.

It's not really an optimisation as such. It's disambiguating syntax, both for the user (I do believe that knowing it's always a struct type is helpful) and for tools - it means that current Go parsers can parse the new syntax, even if they can't understand the new semantics.

@urandom
Copy link

urandom commented Nov 5, 2019

Just for disambiguation, wouldn't it be better to use _{} for maps/slices as opposed to structs, since they are less likely to occur in the wild, while keeping structs representations as {}?

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Nov 5, 2019

Just for disambiguation, wouldn't it be better to use _{} for maps/slices as opposed to structs, since they are less likely to occur in the wild

Firstly, one main reason for using _{} is to keep the syntax backwardly compatible and unambiguous, which wouldn't be the case if {} is used.

Secondly, I'm not sure that an untyped map/slice literal is as useful - as you say, they're less likely to occur in the wild, and also it's much less common to have significant numbers of large map/slice types that aren't easy to type. If you've just got a few such types, it's easy to define your own alias:

type m = map[something]somethingElse
var foo = m{"a": "b"}

@neild
Copy link
Contributor

neild commented Nov 5, 2019

It's not really an optimisation as such. It's disambiguating syntax, both for the user (I do believe that knowing it's always a struct type is helpful) and for tools - it means that current Go parsers can parse the new syntax, even if they can't understand the new semantics.

When you say disambiguating syntax, what are the specific ambiguities you're thinking of?

I think it would also be useful to be very clear about when considerations apply to a non-nested _{} literal and one occurring inside some other composite literal. The motivating example introducing this proposal (it is "extraordinarily hard to write literals for some types") is entirely concerned with nested composite literals; writing the top-level type is not generally a problem.

@bradfitz
Copy link
Contributor

bradfitz commented Nov 19, 2019

To continue the recent theme (#35386, #33359) of proposing new uses for ...:

    v := struct{...}{Name: "Bob", Age: 12}

@ianlancetaylor
Copy link
Contributor

I don't really like the idea that _{} implies an anonymous struct. @bradfitz 's suggestion of struct{...} is longer but clearer. It also roughly corresponds to the [...]int{} array syntax.

@bradfitz
Copy link
Contributor

This would help out with text/template & html/template usage. Grepping for Execute.*struct I find code like:

        err := notifyTmpl.Execute(&msg, struct {
                Builder  string
                LogHash  string
                Hostname string
        }{builder, logHash, domain})

and

func emailBody(page, diff string) (string, error) {
        var buf bytes.Buffer
        if err := htmlTmpl.Execute(&buf, struct {
                PageURL, Diff string
        }{
                Diff:    diff,
                PageURL: fmt.Sprintf("https://golang.org/wiki/%s", page),
        }); err != nil {

@griesemer
Copy link
Contributor

griesemer commented Nov 19, 2019

[edited: added missing keys in struct literal]

Presumably one could write:

var s struct{x int; y float32} = struct{...}{x: 1, y: 2}

and the 2 in the struct literal would become a float32 in the assignment?

@ianlancetaylor
Copy link
Contributor

I think the constant part of this proposal is basically the same as #6386. We can probably consider that separately.

@jimmyfrasche
Copy link
Member

@griesemer I would personally expect your example to be a compile time error. I'd expect that the literal must be keyed and that the usual rules would apply for default types so that it would need to be written

var s struct{x int; y float32} = struct{...}{x: 1, y: float32(2)}

Even then I'm not sure how I feel about that since the order of the keys would matter. struct{...}{a: 1, b: 2} would have type struct { a, b int } but struct{...}{b: 1, a: 2} would have type struct { b, a int }.

Something like #12854 seems a better fit for this particular example, where the below would suffice

var s struct{x int; y float32} = {1, 2}

@rogpeppe
Copy link
Contributor Author

@griesemer wrote:

Presumably one could write:

var s struct{x int; y float32} = struct{...}{1, 2}

and the 2 in the struct literal would become a float32 in the assignment?

Yes, that's my main reason for including const structs as part of this proposal.
The value on the right hand side is a constant struct value, and can be assigned
as described in this rule (from the proposal description):

An anonymous struct literal can be assigned to any struct type that contains a superset of the fields in the literal. Any const fields are treated with the usual const assignment rules. Fields that are not mentioned will be zero.

@ianlancetaylor wrote:

I think the constant part of this proposal is basically the same as #6386. We can probably consider that separately.

That proposal also includes constant arrays and slices, which aren't be considered here and are perhaps of more marginal use.

@urandom
Copy link

urandom commented Nov 20, 2019

Perhaps, rather than allowing a const struct to be assigned to a superset struct, only allow assignment to a comparable one.

For supersets, we could propose yet another usage of ..., and allow destructing assignment of any struct to another one, as is currently allowed for slices and varargs:

r := struct{x: int, z: string}{42, "foo"}
s := struct{x: int, y: float32, z: string}{r..., y: 0.5}

This doesn't fall within the boundaries of this particular proposal, though it might be worthwhile to keep in mind lest we end up with potentially multiple ways of doing a very similar thing.

@bradfitz bradfitz added the dotdotdot ... label Nov 26, 2019
@ianlancetaylor
Copy link
Contributor

@rogpeppe Any thoughts on struct{...} suggested in #35304 (comment) ?

I see your earlier comment that this is more verbose. Yes, it is. But it seems more clear than _, which in other contexts implies discarding a value.

@rogpeppe
Copy link
Contributor Author

I take your point about _ usually implying a discarded value. But, at 11 characters, struct{...} does seem to me to be more verbose than I'd like for this feature.

Two other possibilities:

  • we could define builtin type name for this... but I haven't managed to think of a good name.
  • I think it might be viable to use ... itself. For example:
v := ...{Name: "Bob", Age: 12}

@urandom
Copy link

urandom commented Nov 28, 2019

To me, ...{} looks like you'd somehow be defining a slice of these structs

@cristaloleg
Copy link

Proposed syntax struct{...}{Foo: "bar"} by Brad plays quite well with [...]int{1, 2, 3} and looks natural in Go.

We do not reuse _ in a different context (original context is about skipping variables, right?), optimising 1 vs 11 characters is an overkill (we won more by dropping {Foo string, Bar int} in a struct declaration).

(basically what Ian mentioned #35304 (comment))

@alanfo
Copy link

alanfo commented Nov 28, 2019

Although I prefer struct{...} to the other suggestions made so far, I agree with @rogpeppe that it is rather long.

Another possibility would be to stick with the struct keyword but use parentheses rather than braces:

v := struct(Name: "Bob", Age: 12)

// or if type inference is to be allowed

var v struct{name string; age int} = struct("Bob", 12)

As struct is a keyword, this couldn't be mistaken for a function call.

If a new built-in type were to be used instead, then tuple would be a good name for it as that, in effect, is what it is:

v := tuple{Name: "Bob", Age: 12}

// or in type inferred form

var v struct{name string; age int} = tuple{"Bob", 12}

Although introducing a new built-in type name may seem like an expensive way of dealing with this particular problem, it might find wider usage as a kind of generic struct. For example as a function parameter or return type to which compatible structs could be assigned.

Also, if const structs were introduced where all the field types were themselves const types, then tuple could serve as an 'untyped' form of such constants.

@mvdan
Copy link
Member

mvdan commented Nov 28, 2019

Is ... too verbose to type, or to see/read? I think in Go we're generally okay with needing a few extra characters to gain some clarity.

@DmitriyMV
Copy link

Another possibility would be to stick with the struct keyword but use parentheses rather than braces:

Disagree. That will make struct a context dependent word, which Go generally try to avoid.

If a new built-in type were to be used instead, then tuple would be a good name for it as that

That would actually introduce a new metatype since tuple(int, float) != tuple(float, int). Also there is a different issue about the type inference.

@alanfo
Copy link

alanfo commented Nov 28, 2019

@mvdan

Is ... too verbose to type, or to see/read?

Well, the extra typing would actually be {...} rather than just ....

As I said earlier I certainly prefer this to those suggestions which just use a symbol (these look more like Perl than Go to me), but the fact remains that it is verbose and hence my alternative suggestions.

@rogpeppe
Copy link
Contributor Author

Is ... too verbose to type, or to see/read? I think in Go we're generally okay with needing a few extra characters to gain some clarity.

For the record, I was comparing the suggested struct{...} with the originally proposed _ (11 characters vs 1).

@alanfo
Copy link

alanfo commented Nov 28, 2019

@DmitriyMV

That will make struct a context dependent word, which Go generally try to avoid.

If you mean that struct (which is a 'full' keyword, not a 'contextual' one) could now be followed by either a ( or a { then, of course, I agree with you though I don't think this is necessarily a fatal objection.

That would actually introduce a new metatype since tuple(int, float) != tuple(float, int).

Agreed again. As far as type inference is concerned, if something like this were allowed:

const c = tuple {12, 12.0}

then c would only be compatible with struct {int; float64}, not struct {float64; int) since, in the absence of other info, the default types of 12 and 12.0 would need to be used to infer the types of the fields.

@mdcfrancis
Copy link

perhaps add a typed Tuple which is implicitly a const? The syntax directly parallels that of the arguments to a function call / return specification

        t := ( "hello", "world", 123 ) // Define a const tuple of type  (string, string, int )
        // Or explicitly ( "hello", "world" string, 123 int )
	t[0] == "hello"
	t[2] == 123
	t[0] = "update" // Compile time error

this could open up a number of paths. Firstly collecting generic args and passing them in a compile time typed way, this is not possible today and you have to shift to ...interface{}.

        // Using tuples this could work 
        func f( args ...tuple )  {
             g( args... ) 
        }
        func g( a string, b int ) { }
        func main() {
             f( "hello", 123 ) 
        }

collecting return args, if multiple return types are implemented as tuples you end up being able to do something like

     func f() ( string, int, int ) { return "hello", 1, 2 }
     arg1, ...args := f() // where args is tuple ( int, int) 

enabling tuple operations

    a, _, b  := ( "hello", 1.23, "world" )
    a == "hello"
    b == "world"

Finally a move towards the anonymous typed struct by supporting a named tuple syntax.

	n := ( f1  : "hello", f2 : 123)
	// n := ( f1 string : "hello", f2 int : 123)
	
        n[0] == n.f1 == "hello"
	n[1] == n.f2 == 123
	n.f2 = 345 // Compile time error

@tandr
Copy link

tandr commented Mar 12, 2020

t[0] = "hello"

That's going to be very easy to confuse with array access, sorry.
Maybe
t.0 == "hello"
or
t.[0] == "hello"

would make it more readable?

@rustyx
Copy link

rustyx commented Oct 1, 2021

Why isn't &_{Name: "Bob"} allowed? Sounds like a nice way to express an anonymous struct without typing out all of its types.

I think this should be allowed:

data := _{X: x, Y: y}

Or like this perhaps

data := struct _ {X: x, Y: y}

@ghost
Copy link

ghost commented Dec 23, 2022

I wish it was possible to use _ in place of an empty struct literal, like this:

m := make(map[int]struct{})
m[0] = _

or even this:

m := make(map[int]_)
m[0] = _

Is there a proposal for that?

@ianlancetaylor
Copy link
Contributor

@opennota I don't know of an open proposal for that. It's been proposed at least at https://groups.google.com/g/golang-nuts/c/kBOnfs3a2tU/m/YxTuqMLhYckJ and https://groups.google.com/g/golang-dev/c/iAysKGpniLw/m/qSbtBUx4-sMJ . There is also https://go.dev/issue/35966.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dotdotdot ... LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests