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: spec: allow constants of arbitrary data structure type #6386

Open
gopherbot opened this issue Sep 14, 2013 · 21 comments

Comments

@gopherbot
Copy link

commented Sep 14, 2013

by RickySeltzer:

var each1 = []byte{'e', 'a', 'c', 'h'}
    const each2 = []byte{'e', 'a', 'c', 'h'}

The 'var' is accepted, the 'const' is not. This is a defect in the language spec and
design.

1. What is a short input program that triggers the error?
http://play.golang.org/p/Jbo9waCn_h

2. What is the full compiler output?
prog.go:7: const initializer []byte literal is not a constant
 [process exited with non-zero status]
@robpike

This comment has been minimized.

Copy link
Contributor

commented Sep 14, 2013

Comment 1:

Issue #6388 has been merged into this issue.

@robpike

This comment has been minimized.

Copy link
Contributor

commented Sep 14, 2013

Comment 2:

Labels changed: added priority-someday, languagechange, removed priority-triage, go1.2maybe.

Status changed to Accepted.

@cznic

This comment has been minimized.

Copy link
Contributor

commented Sep 16, 2013

Comment 3:

I'm against this. If it would have to have constant semantics then its run time costs
are the same as today, only hidden.
        const c = []byte{1}
        a := c
        a[0] = 42
        b := c
        fmt.Println(b[0] == 1)
The above can print 'true' only if the c's backing array is copied in assignment to 'a',
however the const declaration gives an illusion of always using the same backing array -
like is the case of a string's backing array.
IOW: 1. nothing is gained by const []T and 2. run time costs get hidden and thus
potentially confusing.
@gopherbot

This comment has been minimized.

Copy link
Author

commented Sep 16, 2013

Comment 4 by RickySeltzer:

If slice constants have this hidden cost problem, could we at least have constant arrays
and arrays of arrays?
 const sentence = [...][...]byte{"a", "series", "of", "pieces", "of", "text"}
1. This should be sufficiently const that it could, in the right environment, go into
rom or the code segment.  That is, be immutable, ideally.
2. I should be able to type it without getting RSI from repeating '[]byte' for every
word.
3. For byte structures like this, it isn't absolutely necessary that we would be allowed
to enter arbitrary runes, although that would be technically feasible, and useful. 
Especially for those who aren't English-language programmers.  And it would be
consistent with the rest of Go.
@gopherbot

This comment has been minimized.

Copy link
Author

commented Sep 16, 2013

Comment 5 by RickySeltzer:

Change "arbitrary runes" ==> "arbitrary Unicode characters with large code points".
@griesemer

This comment has been minimized.

Copy link
Contributor

commented Oct 15, 2013

Comment 6:

Some comments:
1) This is neither a defect of the language nor the design. The language was
_deliberately_ designed to only permit constant of basic types.
2) The implications of such a change are much more far-fetching than meets the eye:
there are numerous open questions that would have to be answered _satisfactorily_; and I
don't think we are there yet.
For instance, if we allow such constants, where is the limit? Do we allow constant maps?
What about constant channels? Constant pointers? Is it just a special case for slices?
etc.
A first step might be to allow constant arrays and structs as long as they are only
composed of fields that can have constant types themselves.
An even smaller step (which I do think we should do) is to make "foo"[i] a constant if i
is a constant (right now it's a non-constant byte).
Finally, note that it's often not very hard for a compiler to detect that a
package-level variable is never modified. Thus, an implementation may choose to optimize
the variable initialization and possibly compute it compile time. At this point, the
const declaration only serves as documentation and for safety; there's no performance
loss anymore.
But again, we have tried to keep the type system (incl. what constants are) relatively
simple so that it doesn't get into the way. It's not clear the benefits are worth the
price in additional complexity.

Status changed to LongTerm.

@gopherbot

This comment has been minimized.

Copy link
Author

commented Oct 19, 2013

Comment 7 by RickySeltzer:

In many projects I have programmed, there is a need for non-writable initialized data
larger than a single variable.  This is analogous to an 'asm' directive that initializes
bits in the code segment.  Something that the compiler has to do anyway.
Also, embedded systems often need to put data into read-only-memory (ROM).  
This has more to do with storage class than the type system.  Declarations rather like
the following might be simplest:
const (
   data =  []byte("The quick brown fox jumped over the lazy dog.")
   π = float64(3.14159)
   bits = []uint32{0x12345678, 0xBabeAbed}
)
I don't see any additional complexity here.  But it might be easier to introduce a new
modifier, "readonly" to supplement "const", if for some reason the above chokes the
grammar. 
Putting data in the code segment is just my way of saying that it's simple.  Actually,
it would be a security risk.  Anything marked "const" or "readonly" should be NX (Not
eXecutable).
@gopherbot

This comment has been minimized.

Copy link
Author

commented Oct 19, 2013

Comment 8 by RickySeltzer:

Also, the language and runtime currently make strings immutable.  So the "Hidden cost"
problem in #4, above, already exists.  Just extend this to const.
@cznic

This comment has been minimized.

Copy link
Contributor

commented Oct 19, 2013

Comment 9:

@8: No, you cannot do &str[expr], nor you can do str[expr] = expr, but you can of all of
that with a []byte, for example.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Nov 27, 2013

Comment 10:

Labels changed: added go1.3maybe.

@gopherbot

This comment has been minimized.

Copy link
Author

commented Nov 29, 2013

Comment 11 by RickySeltzer:

Ah.  It was a typo on my part to use slice notation []byte, instead of array notation,
[...]byte.  I don't propose constant slices.  I propose constant (initialized) arrays. 
See here: http://play.golang.org/p/eCn6ip--w0.
@rsc

This comment has been minimized.

Copy link
Contributor

commented Dec 4, 2013

Comment 12:

Labels changed: added release-none, removed go1.3maybe.

@rsc

This comment has been minimized.

Copy link
Contributor

commented Dec 4, 2013

Comment 13:

Labels changed: added repo-main.

@OneOfOne

This comment has been minimized.

Copy link
Contributor

commented Feb 12, 2016

any news about this?

It'd be nice to have something like const keys = [...]string{"a", "b", ... }

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Feb 12, 2016

This will not change in the foreseeable future.

The bar for language changes is extremely high at this point. "It'd be nice" is certainly not sufficient even as a starting point. To have a chance of even just being considered, there would need to be a full proposal together with a detailed analysis of cost and benefit.

In this specific case, extending the concept of constants to other than just basic types would be a significant change with all kinds of repercussions. I like to add also to the comment in the initial issue report that the current situation is not a "defect" in the spec - it was conceived as is pretty much from day one, for very good reasons.

Leaving open for a future Go 2 if there will ever be one.

@griesemer griesemer added the Go2 label Feb 12, 2016

snsinfu added a commit to snsinfu/learn-go that referenced this issue May 8, 2017
02: Simple word counter
This is a simple state machine implementation.

- Go has no array constants [1]
- Go has no builtin enum, use an idiom [2]

[1]: golang/go#6386
[2]: http://stackoverflow.com/a/14426447/5266681

@rsc rsc changed the title spec: array constants spec: allow constants of arbitrary data structure type Jun 17, 2017

@rsc rsc changed the title spec: allow constants of arbitrary data structure type proposal: spec: allow constants of arbitrary data structure type Jun 17, 2017

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Dec 6, 2017

This is about whether Go should have immutable values. There are a number of things to consider.

One thing to consider is how to handle

const s = [...]int{ f() }

That is, do the elements of an const array have to be const themselves? Is it possible to set up such an array in an init function?

People have already raised questions about const slices. Another question is whether const values are addressable. If I can take a pointer to a const value, such as a field in a const struct, presumably I can't modify the value through that pointer, but what stops me from doing that? This proposal doesn't have any language mechanism for distinguishing a normal pointer from a pointer to a const (which I think is a good thing). So something has to catch erroneous writes and, presumably, panic. If we can put the initializer in read-only memory then I guess that will happen automatically, but then it's hard to initialize the elements in a function.

One idea I've mentioned elsewhere is a freeze function, that would do a shallow copy of a value into memory that is then made read-only (somehow). I don't know if that can be implemented efficiently but it is an approach to this general problem that does not involve a language change.

@jaekwon

This comment has been minimized.

Copy link

commented Dec 17, 2017

Update: If you follow the link I pasted below, you'll see how I realized that module-level state is bad(tm). Please check @wora's proposal thread.

This discussion is pretty open ended, because we're talking about at least two concerns... one about deep immutability, and one about shallow immutability.

I propose that we introduce shallow immutability only. See the proposal for the const-mutable type.

In the above proposal, const is just another kind of var except you can't change what it points to, e.g. it's not addressable. It's got nothing to do with deep immutability, and I imagine it would be fairly easy to implement even for Go1. Combined with the proposal for read-only slices, you get a sufficiently complete set of orthogonal language rules that allow you to program safe and performant code.

@bcmills

This comment has been minimized.

Copy link
Member

commented Mar 3, 2018

That is, do the elements of an const array have to be const themselves? Is it possible to set up such an array in an init function?

In the compile-time functions proposal, I proposed that package-level functions could themselves be marked const (i.e., made available at compile time), where the arguments to those functions could be:

  • constants (including constants of type gotype)
  • calls to other compile-time functions (even if they do not return constants)
  • functions declared at package scope
  • functions and variables declared locally within other compile-time functions
  • function literals, provided that the body of the literal does not refer to the local variables of a run-time function or method

The focus of that proposal was on computing types, but you could fairly easily strip out the first-class types and I think end up with a reasonable proposal for generalizing const to other value types (but not slices or pointers).

It would, however, be a lot of work to implement.

@jimmyfrasche

This comment has been minimized.

Copy link
Member

commented Jul 21, 2018

I wanted to think through what this would mean:

  • Allow arrays, [N]T, to be constant when T can be a constant.
  • Allow structs to be constant when all of its fields are types that can be constants.

Note: This is not an argument against constant slices, maps, etc.; just an exploration of what allowing only the above to be constants would mean.

To answer @ianlancetaylor's questions, these, like other constants, are not addressable (nor are their indices/fields), cannot be created in init() functions, and const s [...]int{ f() } would be illegal unless f() is a constant expression. Each element or field is itself constant (so ca[0] = v and cs.f = v are both illegal).

This would make

const (
  x int = iota
  y
  z
)

and

const vec = [...]int{0, 1, 2}

and

const vec = struct {
  x, y, z int
}{1, 2, 3}

all equivalent ways of organizing and labeling the same constants.

Constant arrays

Constant arrays being inaddressable has a major downside: they cannot be sliced (unless constant slices are also allowed). The primary use cases for constant arrays are, as far as I am aware, tables of precomputed values and file/network signatures. None of these especially require slicing. It would still be very inconvenient, though.

It would be possible to define slicing a constant array to always duplicate the backing array but this would make an expensive operation look like an expensive one. No go.

Another option would be to extend the copy builtin to take arrays, so that copy(s, a) behaved like copy(s, a[:]) does now. This would make the expense of the operation clear.

That's still a bit awkward. It would be nice, but not essential, to also define a new built in, dup, which takes a slice or an array and returns a slice with a copy of the (backing) array. (µ-experience report: I've written similar for byte slices at least a dozen times). This would make it easier to convert a constant array into a slice as part of an expression and allow the compiler to optimize the copy out when possible, like for _, v := range dup(ca)[1:] {. Limited to slices, it could be written as a generic function but would need to be a builtin to operate on arrays directly so it only makes sense to consider it if there are constant arrays.

iota in a constant array should behave the same way as iota in any other const block so

const (
  a, x = [2]int{iota, iota + 1}, iota
  b, y
  c, z
)

would result in a = [2]int{0, 1}, x = 0, b = [2]int{1, 2}, y = 1, c = [2]int{2, 3}, and z = 2.

It is unclear to me whether the below, in whole or in part, should be allowed:

const (
  a = [1+iota]int{iota: iota}
  b
  c
)

However, if it is, it should result in a = [1]int{0}, b = [2]int{0, 1}, and c = [3]int{0, 0, 2}. While logical, the utility is unclear.

Constant structs

Since constant structs are free of pointers, they can always be passed by value without aliasing. Passing a constant struct to a function duplicates the original and the duplicate is then mutable (thought the compiler would be free to elide the duplication when it can prove an absence of mutation).

Given

type T struct {
  a, b, c int
}
var X = T{1, 2, 3}
const Y = T{1, 2, 3}

the only differences between X and Y is that the initial definition of Y cannot be changed and pointer methods cannot be called directly on Y.

A rough equivalent that can be done today would be to define Y as

func Y() T {
  return T{1, 2, 3}
}

That offers the same safety guarantees and, I'm sure, some of the optimization opportunities, but it's more verbose and makes it awkward to define large sets of pseudo-constants without code generation.

Allowing constant structs would be useful for sentinel/named values that should never be changed or grouping related constants into a single complex.

(Everything said about constant structs would apply to constant discriminated unions, should those be added).

Discussion

I can certainly see better why constants were limited to basic types. The gains of extending constants to the product types—without adding immutability to the language—are not mind blowing. If these were allowed, I'd certainly use them with carefree abandon, but they don't seem to quite clear the bar by themselves.

Combined with something like #23637 (comment) I could see it being worth the effort, since it provides a more concrete need and additional value than they have on their own.

Being able to use iota when defining a lot of named struct values would be useful, though @griesemer proposed allowing iota in var blocks in #21473. That would not disallow allowing another package to change the value, but, as disquieting as that prospect is, it doesn't seem to have caused any serious issues in practice.

@networkimprov

This comment has been minimized.

Copy link

commented Aug 7, 2018

See also #21130

@go101

This comment has been minimized.

Copy link

commented Feb 2, 2019

An alternative proposal for the same goal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.