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)
}
Related
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"
}
Is there a way to add a tag or annotate some methods so that whenever an event arises all the methods with this tag or annotation get invoked?
How to achieve this in Go?
As others have said, no there are no annotations or tags on functions in Go.
However, it is more than possible to use a map (or slice) and a register function to keep records of other funcs (or interfaces). You just need to manually call that register func (usually in init() of that package).
For example,
package events
var eventFuncs = make(map[string]func())
func Register(event string, f func()) {
eventFuncs[event] = append(eventFuncs[event],f)
}
func Get(event string) []func() {
return eventFuncs[event]
}
And using the package to register:
package xy
import (
"fmt"
"some.url/events"
)
func Call() {
fmt.Println("Ha!")
}
func init() {
events.Register("x",Call)
}
And then call the funcs elsewhere:
package main
import (
"some.url/events"
)
func main() {
for _,f := range pkg.GetEvent("x") { f() }
}
Today I try to program with context,code as follow:
package main
func main(){
ctx := context.Background()
ctx = context.WithValue(ctx,"appid","test111")
b.dosomething()
}
package b
func dosomething(ctx context.Context){
fmt.Println(ctx.Value("appid").(string))
}
Then my program has crashed.I think it's due to that these ctx is in different package
I suggest you to use context only in a lifetime of a single task and pass the same context through functions.
Also you should understand where to use context and where just to pass arguments to functions.
Another suggestion is to use custom types for setting and getting values from context.
According to all above, you program should look like this:
package main
import (
"context"
"fmt"
)
type KeyMsg string
func main() {
ctx := context.WithValue(context.Background(), KeyMsg("msg"), "hello")
DoSomething(ctx)
}
// DoSomething accepts context value, retrieves message by KeyMsg and prints it.
func DoSomething(ctx context.Context) {
msg, ok := ctx.Value(KeyMsg("msg")).(string)
if !ok {
return
}
fmt.Println("got msg:", msg)
}
You can move function DoSomething into another package and just call it as packagename.DoSomething it will change nothing.
Here is my directory structure:
my_project
my_api
main.go
util_dir
util.go
I am passing some environment variables in the init function inside the main.go since I want them to be sourced whenever my service starts.
Below is the main.go code snippet:
import (
"net/url"
"net"
)
func init() {
...
data := url.Values{}
data.Add("client_id", os.Getenv("CLIENTID"))
data.Add("client_secret", os.Getenv("CLIENTSECRET"))
}
I want to do something like this inside util.go :
import (
"os"
"github.com/my_project/my_api"
)
func validation() {
data.data.Add("scope", "xyzid")
data.data.Add("another_secret", "SECRET")
client := &http.Client{}
req, err := http.NewRequest("POST", urlStr, bytes.NewBufferString(data.data.Encode()))
}
I am getting an error that import github.com/my_project/my_api is a program, not an importable package.
I want to know what is a work around for this problem?
You can make a separate, third package that has your init function, which can export the values read from the environment variables. Then, both your main package and utility directory can read from them. Just by importing the package the init function will be read, so you can be sure the values are initialized in any program that imports it.
import github.com/my_project/my_api is a program, not an importable
package
above error said clearly that you cant import a program because it is not a package.
From what I can understand is you wanted your data variable to be shared across the package.
data := url.Values{} // you call this in your main package.
you can do this by creating another package in your util or somewhere else :
package otherPackage
var Data url.Values{}
func InitData(){
// begin your init data
Data = url.Values{}
Data.Add("client_id", os.Getenv("CLIENTID"))
Data.Add("client_secret", os.Getenv("CLIENTSECRET"))
}
and call this in your main package like this :
package main
import (
....
"github.com/yourProject/otherPackage"
....
)
func init(){
ohterPakcage.InitData()
}
now you have a global variable called Data and you can call it in another package like :
otherPackage.Data // do what you want with that variable.
hope this help you.
import (
"net/url"
"net"
)
var Data = init()
func init() url.Values {
...
data := url.Values{}
data.Add("client_id", os.Getenv("CLIENTID"))
data.Add("client_secret", os.Getenv("CLIENTSECRET"))
return data
}
now in util.go you can use data like blow
import (
"os"
"github.com/my_project/my_api"
)
func validation() {
my_api.Data.Add("scope", "xyzid")
my_api.Data.Add("another_secret", "SECRET")
client := &http.Client{}
req, err := http.NewRequest("POST", urlStr, bytes.NewBufferString(data.data.Encode()))
}
I'm trying to use Go's reflection system to retrieve the name of a function but I get an empty string when calling the Name method on its type. Is this the expected behavior?
This is a simple example of how I approach the problem:
package main
import "fmt"
import "reflect"
func main() {
typ := reflect.TypeOf(main)
name := typ.Name()
fmt.Println("Name of function" + name)
}
The solution is to use FuncForPc which returns a *Func.
This returns "main.main" :
package main
import "fmt"
import "reflect"
import "runtime"
func main() {
name := runtime.FuncForPC(reflect.ValueOf(main).Pointer()).Name()
fmt.Println("Name of function : " + name)
}
If you want "main", just tokenize it.
package main
import "fmt"
import "runtime"
func main() {
pc, _, _, _ := runtime.Caller(0)
fmt.Println("Name of function: " + runtime.FuncForPC(pc).Name())
fmt.Println()
// or, define a function for it
fmt.Println("Name of function: " + funcName())
x()
}
func funcName() string {
pc, _, _, _ := runtime.Caller(1)
return runtime.FuncForPC(pc).Name()
}
func x() {
fmt.Println("Name of function: " + funcName())
y()
}
func y() {
fmt.Println("Name of function: " + funcName())
z()
}
func z() {
fmt.Println("Name of function: " + funcName())
}
Output:
Name of function: main.main
Name of function: main.main
Name of function: main.x
Name of function: main.y
Name of function: main.z
import runtime
func funcName() string {
pc, _, _, _ := runtime.Caller(1)
nameFull := runtime.FuncForPC(pc).Name() // main.foo
nameEnd := filepath.Ext(nameFull) // .foo
name := strings.TrimPrefix(nameEnd, ".") // foo
return name
}
This is a tested production ready utility function for returning function name.
Note 1: We are handling the possibility of a nil pointer from FuncForPC
Note 2: optFuncLevel is just a friendly name for stack frame level. This gives us the flexibility of using this within another layer of utility functions.
A direct call from say main would just pass 1 (or nothing since default), but if I am calling FunctionName in a log enriching function, say PrettyLog() that is called from regular code, I would call it as FunctionName(2) in the call from PrettyLog, so the function name returned is the name of the caller of PrettyLog, not PrettyLog itself.
// FunctionName returns the function name of the caller
// optFuncLevel passes the function level to go back up.
// The default is 1, referring to the caller of this function
func FunctionName(optFuncLevel ...int) (funcName string) {
frameLevel := 1 // default to the caller's frame
if len(optFuncLevel) > 0 {
frameLevel = optFuncLevel[0]
}
if pc, _, _, ok := runtime.Caller(frameLevel); ok {
fPtr := runtime.FuncForPC(pc)
if fPtr == nil {
return
}
// Shorten full function name a bit
farr := strings.SplitN(fPtr.Name(), "/", 2)
if len(farr) < 2 {
return
}
return farr[1]
}
return
}