Go language: duplicate string constants compilation - go

I'm considering writing a template to Go code generator and wonder:
If I have many string constants and/or variables with the same value would they be stored in an executable file multiple times or compiler will make an optimisation and store just 1 instance per duplicate and refer it wherever needed?
E.g.
File1.go
========
s1 := "some_string_1"
s2 := "some_string_2"
FileN.go
========
a1 := "some_string_1"
a1 := "some_string_2"
Would it affect file size if I have multiple files with the same constants or compiler is smart enough to make optimization?
Imagine that in many templates I have somthing like:
Template 1
==========
{% for a in list1 %}<td>{{a}}</td>{% endfor %}
Tempalte 2
==========
{% for b in list2 %}<td>{{b.c}}</td><td>{{b.d}}</td>{% endfor %}
that would be translated to something like:
for i, a := range list {
write("<td>")
write(a)
write("</td>")
}
for i, b := range list2 {
write("<td>")
write(b.c)
write("</td></td>")
write(b.d)
write("</td>")
}
Obviously the <td> & </td> would be repeated many times in code. So would it make any sense to create a global array of strings and takes values from it? Something like:
myStrings := [...]string{
"<td>",
"</td>"
}
for i, a := range list {
write(myStrings[0])
write(a)
write(myStrings[1])
}

The language spec says nothing regarding the internal storage of string constants or literals. Based on this discussion I would assume there is currently no interning in the language; https://github.com/golang/go/issues/5160 though that is a bit dated now.
That same search turned up this project which you may find helpful; https://gist.github.com/karlseguin/6570372

Related

How can I join strings in Go template without template functions?

I have a string slice, like x := []string{a,b,c}, and eventually I want it to be like a+"/"+b+"/"+c.
The problem I'm trying to deal with is coming from the Go template.
I want to have a solution in Go template.
The following is my current solution, but it's obviously ugly.
res := {{range item := .x}}item+"/"{{end}}
res = res[:len(res)-1]
If you have better approaches, appreciate it!
Use the following code to join a slice with /:
{{range $i, $v := .x}}{{if $i}}/{{end}}{{$v}}{{end}}
Run the example on the playground.

Golang for seems to be skipping some entities

So I have an out string like this:
out :=
"mobile" + "\n" +
"mobile/communicators" + "\n" +
"mobile/communicators/mock" + "\n" +
"mobile/handlers" + "\n" +
"mobile/mocks" + "\n" +
"mobile/models" + "\n" +
"mobile/requests"
Which I'm trying to transform into a slice and then remove the entries which have matching sub-strings in the pkgsToIgnore slice:
pkgs := strings.Split(strings.TrimSpace(string(out)), "\n")
pkgsToIgnore := []string{"mock", "models"}
for i, pkg := range pkgs {
for _, pkgToIgnore := range pkgsToIgnore {
if strings.Contains(pkg, pkgToIgnore){
pkgs = append(pkgs[:i], pkgs[i+1:]...)
}
}
}
for _, pkg := range pkgs {
fmt.Println(pkg)
}
And then this is the result I get from the Println iterations:
mobile
mobile/communicators
mobile/handlers
mobile/models
mobile/requests
My code seems to work fine for the lines containing mock as a sub-string, as they get removed. But I can't understand why mobile/models doesn't.
There are several ways to do this without accidentally skipping elements. One way is simply to copy the elements you want into a temporary slice and then replace the original once you've done everything (it's much harder to make mistakes that way). But if you don't like that, it helps to use two indices:
src, dst := 0, 0
for src < len(pkgs) {
var ignored bool
for _, pkgToIgnore := range pkgsToIgnore {
if strings.Contains(pkg, pkgToIgnore) {
ignored = true
break
}
}
if !ignored {
pkgs[dst] = pkgs[src]
dst += 1
}
src += 1
}
pkgs = pkgs[:dst]
src will range from 0 to the last index of pkgs; dst will always be less than or equal to src (so we never overwrite an element we haven't seen yet); each element is only copied once, to its final destination, instead of shifting all elements left by one each time an element is removed (which is potentially quadratic). The length of the slice is only adjusted at the end, to reflect the number of elements actually retained.
The easiest way to see what is happening is to output i and pkg during each iteration i.e.
for i, pkg := range pkgs {
fmt.Println(i, pkg)
for _, pkgToIgnore := range pkgsToIgnore {
if strings.Contains(pkg, pkgToIgnore) {
pkgs = append(pkgs[:i], pkgs[i+1:]...)
}
}
}
The output of this is:
0 mobile
1 mobile/communicators
2 mobile/communicators/mock
3 mobile/mocks
4 mobile/requests
5 mobile/requests
6 mobile/requests
This is probably not what you were expecting! The reason for that is that you are removing elements from pkgs whilst iterating over it. Lets work through this when i = 2; pkg == "mobile/communicators/mock" so the string will be removed from pkgs. Element 2 of pkgs is now "mobile/handlers". We loop around to the for and move to the next element (3) which is "mobile/mocks" (not "mobile/handler" - that is now element 2 so "mobile/handler" does not get checked).
The relevant section of the go spec may help in understanding this:
For an array, pointer to array, or slice value a, the index iteration
values are produced in increasing order, starting at element index 0.
If at most one iteration variable is present, the range loop produces
iteration values from 0 up to len(a)-1 and does not index into the
array or slice itself. For a nil slice, the number of iterations is 0.
It is possible to update a slice within a loop like this but doing so requires care; generally its simpler to copy the elements you want to keep into a new slice.

Maintain unordered iteration over map in Go templating

My problem:
I have a map[string]Type which I want to iterate over in a template maintaining the random ordering behaviour of the wider language.
The Go template library states here https://golang.org/pkg/text/template/#hdr-Actions that :
If the value is a map and the
keys are of basic type with a defined order ("comparable"), the
elements will be visited in sorted key order.
I know I can work around this by declaring a separate []string of the keys in the initial map, then iterate over this, i.e.:
data := map[string]DummyStruct{}
data["Windward"] = DummyStruct{"Windward", 15}
data["Phlebas"] = DummyStruct{"Phlebas", 3}
data["Art"] = DummyStruct{"Art", 3}
i := 0
indices := make([]string, len(data))
for name, value := range data {
fmt.Printf("%v, %v\n", name, value)
indices[i] = name
i ++
}
however I was hoping for this being a fully native, supported feature of the templating library to match the behaviour across the wider language, however it doesn't seem to be supported at all.
See the Playground here for a full example: https://play.golang.org/p/1oTI56G5pr9

Go templates range loop is quoting my value

I've got a slice of strings (.Table.PKey.Columns) that I'm trying to loop over in my template to generate a go file that does some appends, but when I output $value in my template, apparently Go is quoting it for me, so it is giving me the error:
5:27: expected selector or type assertion, found 'STRING' "ID"
i.e., instead of the template output looking something like o.ID -- which is what I'm aiming for, it ends up looking something like o."ID" (I presume).
Am I right in my assumption that this is the result of using a range loop? Because it seems when I access variables directly in other places (for example, say I had a string and I did: o.{{.Table.MyString}}) it works fine, but as soon as I try and incorporate a range loop into the mix it seems to be quoting things.
{{- range $key, $value := .Table.PKey.Columns }}
args = append(args, o.{{$value}})
{{ end -}}
Any suggestions? Thank you.
The {{range}} does not quote anything. If you see "ID" in your output, then your input value is "ID" with quotation marks included!
See this example:
func main() {
m := map[string]interface{}{
"Id": "Id1",
"Quoted": `"Id2"`,
"Ids": []string{"Id1", `"Id2"`, "Abc"},
}
t := template.Must(template.New("").Parse(src))
t.Execute(os.Stdout, m)
}
const src = `{{.Id}} {{index .Ids 0}} {{.Quoted}}
{{range $key, $value := .Ids}}{{$value}}
{{end}}
`
Output (try it on the Go Playground):
Id1 Id1 "Id2"
Id1
"Id2"
Abc
If the variable Go Template is rendering is within tags, then Go Template will wrap any strings with quotes for you.
Some options include generating the in code prior to asking Go Template to render it.

Go, is this everything you need to know for assignments and initialization?

It is just mind boggling how many different ways Go has for variable initialization. May be either i don't understand this completely or Go is a big after thought. Lot of things don't feel natural and looks like they added features as they found them missing. Initialization is one of them.
Here is running app on Go playground showing different ways of initialization
Here is what i understand
There are values and pointers. Values are initiated using var = or :=.
:= only works inside the methods
To Create value and reference you use new or &. And they only work on composite types.
There are whole new ways of creating maps and slices
Create slice and maps using either make or var x []int. Noticing there is no = or :=
Are there any easy way to understand all this for newbies? Reading specs gives all this in bits and pieces everywhere.
First, to mention some incorrect statements:
To Create value and reference you use new or &. And they only work on composite types.
new() works on everything. & doesn't
Create slice and maps using either make or var x []int. Noticing there is no = or :=
You can actually use := with x := []int{} or even x := []int(nil). However, the first is only used when you want to make a slice of len 0 that is not nil and the second is never used because var x []int is nicer.
Lets start from the beginning. You initialize variables with var name T. Type inference was added because you can type less and avoid worrying about the name of a type a function returns. So you are able to do var name = f().
If you want to allocate a pointer, you would need to first create the variable being pointed to and then get its pointer:
var x int
var px = &x
Requiring two statements can be bothersome so they introduced a built-in function called new(). This allows you to do it in one statement: var px = new(int)
However, this method of making new pointers still has an issue. What if you are building a massive literal that has structs that expect pointers? This is a very common thing to do. So they added a way (only for composite literals) to handle this.
var x = []*T{
&T{x: 1},
&T{x: 2},
}
In this case, we want to assign different values to fields of the struct the pointers reference. Handling this with new() would be awful so it is allowed.
Whenever a variable is initialized, it is always initialized to its zero value. There are no constructors. This is a problem for some of the builtin types. Specifically slices, maps, and channels. These are complicated structures. They require parameters and initialization beyond memory being set to zero. Users of Go who need to do this simply write initialization functions. So, that is what they did, they wrote make(). neW([]x) returns a pointer to a slice of x while make([]x, 5) returns a slice of x backed by an array of length 5. New returns a pointer while make returns a value. This is an important distinction which is why it is separate.
What about :=? That is a big clusterfuck. They did that to save on typing, but it led to some odd behaviors and the tacked on different rules until it became somewhat usable.
At first, it was a short form of var x = y. However, it was used very very often with multiple returns. And most of those multiple returns were errors. So we very often had:
x, err := f()
But multiple errors show up in your standard function so people started naming things like:
x, err1 := f()
y, err2 := g()
This was ridiculous so they made the rule that := only re-declares if it is not already declared in that scope. But that defeats the point of := so they also tacked on the rule that at least one must be newly declared.
Interestingly enough, this solves 95% of its problems and made it usable. Although the biggest problem I run into with it is that all the variables on the lefthand side must be identifiers. In other words, the following would be invalid because x.y is not an identifier:
x.y, z := f()
This is a leftover of its relation to var which can't declare anything other than an identifier.
As for why := doesn't work outside a function's scope? That was done to make writing the compiler easier. Outside a function, every part starts with a keyword (var, func, package, import). := would mean that declaration would be the only one that doesn't start with a keyword.
So, that is my little rant on the subject. The bottom line is that different forms of declaration are useful in different areas.
Yeah, this was one of those things that I found confusing early as well. I've come up with my own rules of thumb which may or may not be best practices, but they've served me well. Tweaked after comment from Stephen about zero values
Use := as much as possible, let Go infer types
If you just need an empty slice or map (and don't need to set an initial capacity), use {} syntax
s := []string{}
m := map[string]string{}
The only reason to use var is to initialize something to a zero value (pointed out by Stephen in comments) (and yeah, outside of functions you'll need var or const as well):
var ptr *MyStruct // this initializes a nil pointer
var t time.Time // this initializes a zero-valued time.Time
I think your confusion is coming from mixing up the type system with declaration and initialization.
In Go, variables can be declared in two ways: using var, or using :=. var only declares the variable, while := also assigns an initial value to it. For example:
var i int
i = 1
is equivalent to
i := 1
The reason this is possible is that := simply assumes that the type of i is the same as the type of the expression it's being initialized to. So, since the expression 1 has type int, Go knows that i is being declared as an integer.
Note that you can also explicitly declare and initialize a variable with the var keyword as well:
var i int = 1
Those are the only two delcaration/initialization constructs in Go. You're right that using := in the global scope is not allowed. Other than that, the two are interchangable, as long as Go is able to guess what type you're using on the right-hand-side of the := (which is most of the time).
These constructs work with any types. The type system is completely independent from the declaration/initialization syntax. What I mean by that is that there are no special rules about which types you can use with the declaration/initialization syntax - if it's a type, you can use it.
With that in mind, let's walk through your example and explain everything.
Example 1
// Value Type assignments [string, bool, numbers]
// = & := Assignment
// Won't work on pointers?
var value = "Str1"
value2 := "Str2"
This will work with pointers. The key is that you have to be setting these equal to expressions whose types are pointers. Here are some expressions with pointer types:
Any call to new() (for example, new(int) has type *int)
Referencing an existing value (if i has type int, then &i has type *int)
So, to make your example work with pointers:
tmp := "Str1"
var value = &tmp // value has type *int
value2 := new(string)
*value2 = "Str2"
Example 2
// struct assignments
var ref1 = refType{"AGoodName"}
ref2 := refType{"AGoodName2"}
ref3 := &refType{"AGoodName2"}
ref4 := new(refType)
The syntax that you use here, refType{"AGoodName"}, is used to create and initialize a struct in a single expression. The type of this expression is refType.
Note that there is one funky thing here. Normally, you can't take the address of a literal value (for example, &3 is illegal in Go). However, you can take the address of a literal struct value, like you do above with ref3 := &refType{"AGoodName2"}. This seems pretty confusing (and certainly confused me at first). The reason it's allowed is that it's actually just a short-hand syntax for calling ref3 := new(refType) and then initializing it by doing *ref3 = refType{"AGoodName2"}.
Example 3
// arrays, totally new way of assignment now, = or := now won't work now
var array1 [5]int
This syntax is actually equivalent to var i int, except that instead of the type being int, the type is [5]int (an array of five ints). If we wanted to, we could now set each of the values in the array separately:
var array1 [5]int
array1[0] = 0
array1[1] = 1
array1[2] = 2
array1[3] = 3
array1[4] = 4
However, this is pretty tedious. Just like with integers and strings and so on, arrays can have literal values. For example, 1 is a literal value with the type int. The way you create a literal array value in Go is by naming the type explicitly, and then giving the values in brackets (similar to a struct literal) like this:
[5]int{0, 1, 2, 3, 4}
This is a literal value just like any other, so we can use it as an initializer just the same:
var array1 [5]int = [5]int{0, 1, 2, 3, 4}
var array2 = [5]int{0, 1, 2, 3, 4}
array3 := [5]int{0, 1, 2, 3, 4}
Example 4
// slices, some more ways
var slice1 []int
Slices are very similar to arrays in how they are initialized. The only difference is that they aren't fixed at a particular length, so you don't have to give a length parameter when you name the type. Thus, []int{1, 2, 3} is a slice of integers whose length is initially 3 (although could be changed later). So, just like above, we can do:
var slice1 []int = []int{1, 2, 3}
var slice2 = []int{1, 2, 3}
slice3 := []int{1, 2, 3}
Example 5
var slice2 = new([]int)
slice3 := new([]int)
Reasoning about types can get tricky when the types get complex. As I mentioned above, new(T) returns a pointer, which has type *T. This is pretty straightforward when the type is an int (ie, new(int) has type *int). However, it can get confusing when the type itself is also complex, like a slice type. In your example, slice2 and slice3 both have type *[]int. For example:
slice3 := new([]int)
*slice3 = []int{1, 2, 3}
fmt.Println((*slice3)[0]) // Prints 1
You may be confusing new with make. make is for types that need some sort of initialization. For slices, make creates a slice of the given size. For example, make([]int, 5) creates a slice of integers of length 5. make(T) has type T, while new(T) has type *T. This can certainly get confusing. Here are some examples to help sort it out:
a := make([]int, 5) // s is now a slice of 5 integers
b := new([]int) // b points to a slice, but it's not initialized yet
*b = make([]int, 3) // now b points to a slice of 5 integers
Example 6
// maps
var map1 map[int]string
var map2 = new(map[int]string)
Maps, just like slices, need some initialization to work properly. That's why neither map1 nor map2 in the above example are quite ready to be used. You need to use make first:
var map1 map[int]string // Not ready to be used
map1 = make(map[int]string) // Now it can be used
var map2 = new(map[int]string) // Has type *map[int]string; not ready to be used
*map2 = make(map[int]string) // Now *map2 can be used
Extra Notes
There wasn't a really good place to put this above, so I'll just stick it here.
One thing to note in Go is that if you declare a variable without initializing it, it isn't actually uninitialized. Instead, it has a "zero value." This is distinct from C, where uninitialized variables can contain junk data.
Each type has a zero value. The zero values of most types are pretty reasonable. For example, the basic types:
int has zero value 0
bool has zero value false
string has zero value ""
(other numeric values like int8, uint16, float32, etc, all have zero values of 0 or 0.0)
For composite types like structs and arrays, the zero values are recursive. That is, the zero value of an array is an array with all of its entries set to their respective zero values (ie, the zero value of [3]int is [3]int{0, 0, 0} since 0 is the zero value of int).
Another thing to watch out for is that when using the := syntax, certain expressions' types cannot be inferred. The main one to watch out for is nil. So, i := nil will produce a compiler error. The reason for this is that nil is used for all of the pointer types (and a few other types as well), so there's no way for the compiler to know if you mean a nil int pointer, or a nil bool pointer, etc.

Resources