Assuming the following
type User struct {
name string
}
users := make(map[int]User)
users[5] = User{"Steve"}
Why isn't it possible to access the struct instance now stored in the map?
users[5].name = "Mark"
Can anyone shed some light into how to access the map-stored struct, or the logic behind why it's not possible?
Notes
I know that you can achieve this by making a copy of the struct, changing the copy, and copying back into the map -- but that's a costly copy operation.
I also know this can be done by storing struct pointers in my map, but I don't want to do that either.
The fundamental problem is that you can't take the address of an item within a map. You might think the compiler would re-arrange users[5].name = "Mark" into this
(&users[5]).name = "Mark"
But that doesn't compile, giving this error
cannot take the address of users[5]
This gives the maps the freedom to re-order things at will to use memory efficiently.
The only way to change something explicitly in a map is to assign value to it, i.e.
t := users[5]
t.name = "Mark"
users[5] = t
So I think you either have to live with the copy above or live with storing pointers in your map. Storing pointers have the disadvantage of using more memory and more memory allocations, which may outweigh the copying way above - only you and your application can tell that.
A third alternative is to use a slice - your original syntax works perfectly if you change users := make(map[int]User) to users := make([]User, 10)
Maps are typically sparsely filled hash tables which are reallocated when they exceed the threshold. Re-allocation would create issues when someone is holding the pointers to the values
If you are keen on not creating the copy of the object, you can store the pointer to the object itself as the value
When we are referring the map, the value returned is returned "returned by value", if i may borrow the terminology used in function parameters, editing the returned structure does not have any impact on the contents of the map
Related
I want to know which is the best way to create instances of a certain struct based on a map[string]string
My app should process huge files in CSV format and should create an instance of a struct for each row of the file.
I'm already using the encoding/csv/Reader from golang to read the CSV file and create an instance of map[string]string for each row in the file.
So given this file:
columnA, columnB, columnC
a, b, c
My own reader implementation will return this map (each row values with the header):
myMap := map[string]string{
"columnA": "a",
"columnB": "b",
"columnC": "c",
}
(this is just an example in real life the file contains a lot of columns and rows)
so.. at this point I need to create an instance of the struct that is related with the row contents, let say:
type MyStruct struct {
AColumn string
BColumn string
CColumn string
}
My question is what could be the best way to create the instance of the struct using the given map, I have already implemented a version that just copy each value from the map to the struct but the code ended up being very long and tedious:
s := &MyStruct{}
s.AColumn := m["columnA"]
s.AColumn := m["columnB"]
s.AColumn := m["columnC"]
...
I also consider using this library https://github.com/mitchellh/mapstructure but I don't know if using reflection could be the best approach considering that the file is huge and will be using reflection for each row.
Maybe there is no other option but I'm asking just in case someone knows a better approach.
Thanks in advance.
I would say that the idiomatic Go way would be just populating the struct's fields from your map. Go favors explicitness this approach is the more direct and the easiest to read. In other words, your approach is correct.
You could make it slightly nicer by initializing the struct directly:
s := &MyStruct{
AColumn: m["columnA"],
BColumn: m["columnB"],
CColumn: m["columnC"],
}
Now, if your structure has 100s of fields (which is an odd design choice), you may want to leverage some code generation. Otherwise, just go with the straightforward code - it's the best approach in the long term.
I already posted a library that I made for some stuff I have needed sometimes, I've made a MapToStruct fews months ago, I pushed that today to share with you the full library. The library is based in the usage of reflect, I still testing and implementing stuff, you will find some odd comments and these kind of things.
https://github.com/FedeMFernandez/goscript
I Hope it is useful
While playing with Go code, I found out that map values are not addressable. For example,
package main
import "fmt"
func main(){
var mymap map[int]string = make(map[int]string)
mymap[1] = "One"
var myptr *string = &mymap[1]
fmt.Println(*myptr)
}
Generates error
mapaddressable.go:7: cannot take the address of mymap[1]
Whereas, the code,
package main
import "fmt"
func main(){
var mymap map[int]string = make(map[int]string)
mymap[1] = "One"
mystring := mymap[1]
var myptr *string = &mystring
fmt.Println(*myptr)
}
works perfectly fine.
Why is this so? Why have the Go developers chosen to make certain values not addressable? Is this a drawback or a feature of the language?
Edit:
Being from a C++ background, I am not used to this not addressable trend that seems to be prevalent in Go. For example, the following code works just fine:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
map<int,string> mymap;
mymap[1] = "one";
string *myptr = &mymap[1];
cout<<*myptr;
}
It would be nice if somebody could point out why the same addressability cannot be achieved (or intentionally wasn't achieved) in Go.
Well I do not know about the internal Go implementation of maps but most likely it is a kind of hash table. So if you take and save the address of one of its entries and afterwards put another bunch of entries into it, your saved address may be invalid. This is due to internal reorganizations of hash tables when the load factor exceeds a certain threshold and the hash table needs to grow.
Therefore I guess it is not allowed to take the address of one of its entries in order to avoid such errors.
Being from a C++ background.
Why are [Go] map values not addressable?
If all other languages were like C++ there would be no point in having other languages.
C++ is a complex, hard-to-read language.
Remember the Vasa! - Bjarne Stroustrup
Go, by design, is a simple, readable language.
dotGo 2015 - Rob Pike - Simplicity is Complicated
A Go map is a hash map. A deterministic hash function is applied to a map key. The hash value is used to determine the primary map bucket for the entry (key-value pair). A bucket stores one or more map entries. A primary bucket may overflow to secondary buckets. Buckets are implemented as an array. As the number of map entries increases by insertion, the hash function adapts to provide more buckets. The map entries are copied incrementally to a new, larger bucket array. If the number of map entries decreases by deletion, space may be reclaimed.
In summary, a Go map is a dynamic, self-organizing data structure. The memory address of an entry (key-value pair) is not fixed. Therefore, map values are not addressable.
GopherCon 2016 Keith Randall - Inside the Map Implementation
In Go, map value addressability is not necessary.
How can I do it?
I have the list of objects, I want list all and change the name of object.
I have the list and I'm doing a while end send to another function, there I change the name, but the name doesn't save.
Any idea how can I do it?
https://play.golang.org/p/el3FtwC-3U
And if there is any book that I can read to learn more, please.
Thank for helping me =D
In the range loop:
for _, track := range tracks {
// send track to channel to change the name
Working(&track, &c)
}
the track variable is actually a copy of the value contained in the map, because the assignement here works on the Track value type, and in Go, values are copied when assigned.
What you should do instead is use the key of your map and assign the values from within the loop.
for key := range tracks {
t := tracks[key]
// send track to channel to change the name
Working(&t, &c)
tracks[key] = t
}
See https://play.golang.org/p/9naDP3SfNh
I didn't found how to get pointer to value in map so I think you have to use map[string]*Track instead and then work with pointers to Track structure in your Working function.
See https://play.golang.org/p/2XJTcKn1md
If you are trying modify tracks in parallel you are may be looking for something like this https://play.golang.org/p/1GhST34wId
Note missing buffer in chanel and go Working in for range tracks.
As you could probably tell from the below code I am working on a project which creates csv reports from data in mongoDB. After getting the data I need in, I need to structure the data into something more sensible then how it exists in the db, which is fairly horrendous (not my doing) and near impossible to print the way I need it. The structure that makes the most sense to me is a slice (for each document of data) of maps of the name of the data to a structure holding the data for that name. Then I would simply have to loop through the document and stuff values into the structs where they belong.
My implementation of this is
type mongo_essential_data_t struct {
caution string
citation string
caution_note string
}
mongo_rows_struct := make([]map[string]mongo_essential_data_t, len(mongodata_rows))
//setting the values goes like this
mongo_rows_struct[i][data_name].caution_note = fmt.Sprint(k)
//"i" being the document, "k" being the data I want to store
This doesn't work however. When doing "go run" it returns ./answerstest.go:140: cannot assign to mongo_rows_struct[i][data_name].caution_note. I am new to Go and not sure why I am not allowed to do this. I'm sure this is an invalid way to reference that particular data location, if it is even possible to reference it in Go. What is another way to accomplish this setting line? If it is too much work to accomplish this the way I want, I am willing to use a different type of data structure and am open to suggestions.
This is a known issue of Golang, known as issue 3117. You can use a temporary variable to get around it:
var tmp = mongo_rows_struct[i][data_name]
tmp.caution_note = fmt.Sprint(k)
mongo_rows_struct[i][data_name] = tmp
as per my understanding, when you write:
mongo_rows_struct[i][data_name]
compiler will generate code, which will return copy of mongo_essential_data_t struct(since struct in go is value type, not reference type), and
mongo_rows_struct[i][data_name].caution_note = fmt.Sprint(k)
will write new value to that copy. And after that copy will be discarded. Obviously, its not what you expect. So Go compiler generate error to prevent this misunderstanding.
In order to solve this problem you can:
1. Change definition of your data type to
[]map[string]*mongo_essential_data_t
2. Explicitly create copy of your struct, make changes in that copy and write it back to the map
data := mongo_rows_struct[i][data_name]
data.caution_note = fmt.Sprint(k)
mongo_rows_struct[i][data_name] = data
Of course, first solution is preferable because you will avoid unnecessary copying of data
Using Xcode/Cocoa and the ExtAudioFile API, I'm trying to store away AudioBufferList* objects away for later use, but I'm running into trouble with re-accessing the data. These objects are coming from repeated ExtAudioFileRead calls.
I realize that I can't just stuff these AudioBufferList* objects into an NSArray, but I was under the impression that an NSPointerArray would work for this purpose. However, when trying to access audioBufferList->mBuffers[0].mData after storing the audio buffer lists in the NSPointerArray, they just come back zeroed out.
I was memcpying the audioBufferLists to new audioBufferList objects since I'm reusing the same audio buffer list for each ExtAudioFileRead call. I'm not sure if that's sufficient, though, and memcpying the void* audioBufferList->mBuffers[0].mData objects isn't helping either.
What's the easiest way to store these AudioBufferList* objects? Am I on the right track?
AudioBufferLists hold their data in audioBufferList.mBuffers[i].mData.
mData is void* and the actual type of the values is determined by the output format you specified.
Example:
If you defined
kAudioFormatFlagsCanonical,
kAudioFormatLinearPCM,
mBitsPerChannel = 32
and
mFramesPerPacket = 1
as your output format, the mData-array contains values of type AudioSampleType (which is Float32 a typedef)
If you have chosen another format the array might contain SInt16 values or something else.
So you must be aware of your output type when you want to copy the contents of mData around.
If you know the format you could simply create a c-array
dataCopy = calloc(dataSize, sizeof(Float32));
and memcpy audioBufferList.mBuffers[i].mData into that.
If you want to use a cocoa NSMutableArray, you would have to wrap the floats into a NSNumber object.