-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: spec: anonymous struct literals #35304
Comments
DiscussionRelated proposalsIssue #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:
I can only tell if x is a field name or an expression by looking at the type of 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:
Zero valueAll types in Go other than struct types and arrays have short representations for their zero value. Pointers, maps, slices and channels have This makes it less verbose to return from struct-returning functions:
It also makes it slightly less verbose to form an empty
Struct constantsThere 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).
wouldn't have those disadvantages. Struct parametersIt'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:
you'd be able to write:
Anonymous map/slice literalsThis 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. CompatibilityThe syntax is compatible with existing Go parsers, so current Updates would be needed to the |
Apologies if it's a bikeshed, but maybe |
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. |
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 "_" ?
would need to be made illegal, since
automatically. (Might not that be that bad actually) Retyping all the examples above to see how it "feels"
(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
would be the same as:
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. |
On Fri, 1 Nov 2019, 20:09 tandr, ***@***.***> wrote:
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
That's actually my mistake, sorry: I am not proposing that they should all
be const any more than I'm proposing that all expressions should be const.
I'll try to fix the wording to make this clearer.
An anonymous struct literal would be a set of expressions just like the
expressions in a call to a function. To pass arguments to a function, we
write positional, comma-separated expressions. In this proposal, the `_{}`
syntax is very similar but works for struct types instead of function
arguments.
This would be valid code, for example:
func main () {
Configure(_{Reader: os.Stdin})
}
type Params struct {
Reader io.Reader
}
func Configure(p Params) error { ... }
Hope that makes it a bit clearer.
|
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: |
In this proposal, this would be valid too, because the
The To recap: the |
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
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? |
I think it would probably be fine if
I think it does matter. There's a big difference between
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:
which is assignable to any struct type that has a numeric field named |
This is two proposals. One is an adjustment to composite literals to permit replacing the name of a struct type with 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.
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 var _ = struct{K T}{K: {V: 0}} // error: missing type in composite literal
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. |
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 type T map[string]string type T struct{key string} Limiting this proposal to structs doesn't simplify the parser's job at all. |
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.
Yup, that's true, thanks. I've adjusted the proposal text accordingly.
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
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.
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:
|
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 conf := samsara.Config{
Net: {
MaxOpenRequests: 10,
}
}
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 |
Yes, and that example is just about OK because, firstly it's clear that
I agree, which is why this proposal would allow that (assuming a
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 |
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 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.
You can't, same as you can't assign an untyped |
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.
It does in this proposal :-) |
In terms of cognitive overhead, i really don't see how adding an extra 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 Adding a |
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 What about code like this (from a real example from when this was tried in the stdlib)?
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. |
Just for disambiguation, wouldn't it be better to use |
Firstly, one main reason for using 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:
|
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 |
I don't really like the idea that |
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 { |
[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 |
I think the constant part of this proposal is basically the same as #6386. We can probably consider that separately. |
@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
Even then I'm not sure how I feel about that since the order of the keys would matter. Something like #12854 seems a better fit for this particular example, where the below would suffice
|
@griesemer wrote:
Yes, that's my main reason for including const structs as part of this proposal.
@ianlancetaylor wrote:
That proposal also includes constant arrays and slices, which aren't be considered here and are perhaps of more marginal use. |
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 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. |
@rogpeppe Any thoughts on I see your earlier comment that this is more verbose. Yes, it is. But it seems more clear than |
I take your point about Two other possibilities:
|
To me, |
Proposed syntax We do not reuse (basically what Ian mentioned #35304 (comment)) |
Although I prefer Another possibility would be to stick with the 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 If a new built-in type were to be used instead, then 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 |
Is |
Disagree. That will make
That would actually introduce a new metatype since tuple(int, float) != tuple(float, int). Also there is a different issue about the type inference. |
Well, the extra typing would actually be 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. |
For the record, I was comparing the suggested |
If you mean that
Agreed again. As far as type inference is concerned, if something like this were allowed: const c = tuple {12, 12.0} then |
perhaps add a typed Tuple which is implicitly a const? The syntax directly parallels that of the arguments to a function call / return specification
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{}.
collecting return args, if multiple return types are implemented as tuples you end up being able to do something like
enabling tuple operations
Finally a move towards the anonymous typed struct by supporting a named tuple syntax.
|
That's going to be very easy to confuse with array access, sorry. would make it more readable? |
Why isn't I think this should be allowed:
Or like this perhaps
|
I wish it was possible to use m := make(map[int]struct{})
m[0] = _ or even this: m := make(map[int]_)
m[0] = _ Is there a proposal for that? |
@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. |
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:
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:
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
_{}
isstruct{}
; the default type of_{Name: "Bob", Age: 12}
isstruct{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:
Unlike other struct literals, it would not be possible to use the
&
operator, so:would be invalid. There might be an argument for allowing assignment of an anonymous struct literal to a pointer type, so:
would be the same as:
This would fit with the way that literal elements in map and slice literals work.
The text was updated successfully, but these errors were encountered: