In golang, I know that map is passed between functions in the form of reference, but I encountered a strange situation today. The running result of the code is not what I imagined. I simplified it into the following lines of code.
.
├── go.mod
├── main.go
├── packageA
│ └── a.go
└── packageB
└── b.go
main.go file
package main
import (
"gostudy/packageA"
"gostudy/packageB"
)
func main() {
packageB.UseMap(packageA.M, packageA.InitMap)
}
a.go
package packageA
var M map[string]string
func InitMap() {
M = make(map[string]string)
M["hello"] = "go"
}
b.go
package packageB
import "fmt"
func UseMap(m map[string]string, callback func()) {
callback()
fmt.Println(m)
}
As you can see, there is only one variable globally declared in the a.go file. I thought the above program should output map[hello:go], but it actually outputs an empty map[]. I'm very confused about this and hope to get an answer.
You're passing the old value of the map as a parameter, before you invoke the function to replace it with a new version of the map.
Let's say packageA.M contains the value map[string]string{"foo": "bar"}. The main() function reads the variable and gets a reference to this map, and passes it and the function to packageB.UseMap().
Inside packageB.UseMap(), your code calls packageA.InitMap() via the callback. This does not modify the existing map; instead, it creates a new map, assigns it to the global variable, and populates it. Anything that had a copy of the old map is unaffected, and the code you show doesn't re-read the value of packageA.M.
I'd recommend dispensing with the global variable entirely: it can make the code hard to test and there are potential problems once you start using goroutines. Just have your setup function return the new map.
package packageA
func InitMap() map[string]string {
return map[string]string{"hello": "go"}
}
package packageB
func UseMap(callback func() map[string]string) {
m := callback()
fmt.Println(m)
}
package main
import "packageA"
import "packageB"
func main() {
packageB.UseMap(packageA.InitMap)
}
Just as a side note to the accepted anwer, if you take a look at this:
// ...
import (
"reflect"
"fmt"
)
// ... other functions
// I defined all of the functions in a single paackage, so I can access them both here
func UseMap(m map[string]string, callback func()) {
fmt.Println(reflect.ValueOf(m).Pointer() == reflect.ValueOf(M).Pointer()) // prints true, they have the same reference
callback()
// inside callback, M global variable takes a whole new reference
// when "M = make(...)"
// and then =>
fmt.Println(reflect.ValueOf(m).Pointer() == reflect.ValueOf(M).Pointer()) // prints false
}
If you want to avoid this without changing your apis, you can do this in your package A:
package packageA
var M map[string]string = make(map[string]string)
func InitMap() {
M["hello"] = "go"
}
Related
As the title asks, is there any way to import a go file from inside of a function?
I was thinking of using a loop for my discordgo(https://pkg.go.dev/github.com/bwmarrin/discordgo) program.
ex :
package main
import (
...
)
func main() {
client, _ := disocrdgo.New("Bot " + mytoken)
...
events, _ := ioutil.ReadDir("./events")
for event, _ := range events {
x, _ := import "MyApp/src/events/" + event // <---
client.AddHandler(x.executor) // using type struct where executor is the function i want to use from the imported file
}
...
}
I feel obligated to precise it so :
Thanks for your SERIOUS answers.
Imports are a compiler concept, so you cannot import packages at runtime (the source code doesn't even exist on the machine running your program, usually).
You can use the registry pattern to get close to what you're looking for.
In the events package, create a function that stores handlers. Call that function in the init function for each event handler package.
In the events package, create another function that adds the stored handlers to a client.
In the main package, import all event handler packages you need and call that second function.
This is more or less how the sql and image packages in the standard library work. See sql.Register and image.RegisterFormat.
// events/registry.go
package events
var handlers = map[string]interface{}{}
func Register(name string, h interface{}) {
handlers[name] = h
}
func ConfigureClient(client *discordgo.Session) {
for _, h := range handlers {
client.AddHandler(h)
}
}
// events/foo/foo.go
package foo
import "MyApp/src/events"
func init() {
events.Register("foo", executor{})
}
type executor struct{}
// events/bar/bar.go
package bar
import "MyApp/src/events"
func init() {
events.Register("bar", executor{})
}
type executor struct{}
// main.go
package main
import (
_ "MyApp/src/events/foo"
_ "MyApp/src/events/bar"
// ...
)
func main() {
client, _ := discordgo.New("Bot " + mytoken)
events.ConfigureClient(client)
}
I have a Go package to manage configuration. The package has a non-exported variable initialized in an init function that holds the configuration data. The user interacts with the configuration through exported functions that internally access the global variable. Something like this
pakage config
var gConfig ...
func init() {
gConfig = ...
}
func Value(name string) (string, error) {
return gConfig.value(name)
}
I’m considering the use of plugins and explore the impact on my config package.
If the plugin imports the config package and calls some of its exported functions, what gConfig variable will be used? Will the plugin have its own internal instance of config with its own gConfig variable initialized when the plugin is loaded, or will the plugin be linked dynamically at load time to use the main program gConfig variable initialized at program startup?
as per documentation
Package plugin implements loading and symbol resolution of Go plugins.
When a plugin is first opened, the init functions of all packages not
already part of the program are called. The main function is not run.
A plugin is only initialized once, and cannot be closed.
Also, you cannot import the same plugin twice.
Will the plugin have its own internal instance of config with its own gConfig variable initialized when the plugin is loaded
the plugin will have its own variable within its scope.
If the plugin imports the config package and calls some of its exported functions, what gConfig variable will be used ?
the variable defined within the package, as you demonstrated.
To check that out, write a small demonstration. Go is very straightforward and efficient, doing it takes very little time, see.
$ tree .
.
├── main.go
├── plug
│ └── plugin.go
└── plugin.so
1 directory, 3 files
// $ cat plug/plugin.go
package main
var pkgGlobal = map[string]string{}
func Set(k, v string) {
pkgGlobal[k] = v
}
func Get(k string) string {
return pkgGlobal[k]
}
// $ cat main.go
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("plugin.so")
if err != nil {
panic(err)
}
var get func(string) string
{
x, err := p.Lookup("Get")
if err != nil {
panic(err)
}
get = x.(func(string) string)
}
var set func(string, string)
{
x, err := p.Lookup("Set")
if err != nil {
panic(err)
}
set = x.(func(string, string))
}
set("tomate", "rouge")
fmt.Println(get("tomate"))
fmt.Println(get("notomate"))
}
build and run
$ go build -buildmode=plugin -o plugin.so plug/plugin.go
$ go run main.go
rouge
I'm writing my first go code and I'm trying to convince myself what I'm doing is not wrong.
anyway, here the project the tree structure.
.
├── helpers
│ └── common.go
├── logger
│ └── util.go
├── logger_example
└── runner.go
The main file to look over here is logger/util.go which look like this.
package logger
import (
"log"
"os"
)
type Logger struct {
*log.Logger
}
func (l *Logger) Info(v ...interface{}) {
l.SetPrefix("Info: ")
l.Println(v...)
}
func (l *Logger) Error(v ...interface{}) {
l.SetPrefix("Error: ")
l.Println(v...)
}
func (l *Logger) Warn(v ...interface{}) {
l.SetPrefix("Warn: ")
l.Println(v...)
}
func (l *Logger) Debug(v ...interface{}) {
l.SetPrefix("Debug: ")
l.Println(v...)
}
func NewLogger() *Logger {
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
return &Logger{logger}
}
As you all can see, I'm just setting the prefix to "INFO | WARN | ERROR | DEBUG"
On the main package I have safely created a Logger instance and VOILA it worked.
Only until I decided to add a helpers package and now things do not look the way I wanted.
since the logger.Logger instance created inside main package, I have to pass it reference to every since package function where I want to invoke the logger statement. (see the below example ..)
// dodly_logger/logger/common.go
package helpers
import "dodly_logger/logger"
func Display(dodlyLogger *logger.Logger) {
dodlyLogger.Info("Inside a helper package")
}
The Main package..
package main
import (
logger "dodly_logger/logger"
helpers "dodly_logger/helpers"
)
func main() {
dodlyLogger := logger.NewLogger()
dodlyLogger.Info("INFO MESSAGE")
dodlyLogger.Error("ERROR MESSAGE")
// Ehh, I have to pass the dodlyLogger ..
helpers.Display(dodlyLogger)
}
Ok, now I know my GOLang knowledge is not complete hence I'm hoping people over here can point me how can I write this more clinically wherein I do not have to pass the reference of the logger.Logger to every function for which I need to log.
Create a package level variable with var.
package main
import (
logger "dodly_logger/logger"
helpers "dodly_logger/helpers"
)
var dlogger logger.Logger
func init() {
dlogger = logger.NewLogger()
}
func main() {
dlogger.Info("Starting Main")
a()
}
func a() {
dlogger.Info("in a")
}
My project is organized as follows
github.com/achanda/poke
├── cmd
│ └── poke.go
├── scanner.go
├── txt_scanner.go
└── types.go
The files are as follows
# cat scanner.go
package poke
type Scanner interface {
Scan() *ScanResult
}
# cat txt_scanner.go
package poke
type txtScanner struct {
txt string
}
func newTxtScanner(host string) Scanner {
return txtScanner{txt}
}
func (tcpcs txtScanner) Scan() *ScanResult {
// do stuff
return &result
}
Now I am trying to call this in my main package (in poke.go) like this
package main
import "github.com/achanda/poke"
func main() {
var sr poke.Scanner
sr = poke.txtScanner{txt}
sr.Scan()
}
This fails to run with
# command-line-arguments
./poke.go:111: cannot refer to unexported name poke.txtScanner
./poke.go:111: undefined: portscan.txtScanner
What am I doing wrong?
you need to access type or field outside package, so you should export them using first letter upper case:
first you should define your txtScanner and txt string with first upper case letter, otherwise you will see this error too:
.\poke.go:8: implicit assignment of unexported field 'txt' in poke.TxtScanner literal
like this:
type TxtScanner struct {
Txt string
}
also see newTxtScanner(host string) function in this working sample codes:
poke.go:
package main
import "github.com/achanda/poke"
func main() {
s := "test"
var sr poke.Scanner
sr = poke.TxtScanner{s}
sr.Scan()
}
txt_scanner.go:
package poke
type TxtScanner struct {
Txt string
}
func newTxtScanner(host string) Scanner {
return TxtScanner{host}
}
func (tcpcs TxtScanner) Scan() *ScanResult {
// do stuff
result := ScanResult{}
return &result
}
types.go:
package poke
type ScanResult struct {
}
scanner.go:
package poke
type Scanner interface {
Scan() *ScanResult
}
I have a small go lang project which in the main.go file has a few handlers that refer to session related methods in a session.go file. Both have package main at the top of the file. The functions in the session.go file all begin with an uppercase letter (i.e. they are public/exported methods). Yet when I run the main.go file, it says the methods located in session.go and called from main.go are undefined. Why is that, how to fix it.
I am running the project like go run main.go
main.go
func logout(w http.ResponseWriter, r *http.Request) {
ClearSession(w, r)
....
}
session.go
func ClearSession(w http.ResponseWriter, r *http.Request) {
}
As #ptd said, the command needs all the files named.
I prefer use another package:
/ main.go
|_session/
|_session.go
|_validations.go
|_errors.go
So, you can organize your code and simplify your named files.
e.g.:
file: main.go
package main
import "session"
func main() {
var validator session.Validator
var session session.Session
...
if session.IsValid() == false {
// return session.InvalidSession
fmt.Printf("ERROR: %v", session.InvalidSession)
}
}
file: errors.go
import "errors"
var (
InvalidSession = errors.New("[Your error message]"
)
Then you can use:
go run main.go