Passing around structs - go

I am new to go and coming from a Ruby background. I am trying to understand code structuring in a world without classes and am probably making the mistake wanting to do it "the Ruby way" in Go.
I am trying to refactor my code to make it more modular / readable so I moved the loading of the configuration file to its own package. Good idea?
package configuration
import "github.com/BurntSushi/toml"
type Config struct {
Temperatures []struct {
Degrees int
Units string
}
}
func Load() Config {
var cnf Config
_, err := toml.DecodeFile("config", &cnf)
if err != nil {
panic(err)
}
return cnf
}
Now, in my main package:
package main
import "./configuration"
var conf Configuration = configuration.Load()
Gives undefined: Config. I understand why. I could copy the struct definition in the main package but that's not very DRY.
It's my understanding passing around structs like this is a bad practice as it makes your code harder to understand (now everyone needs to know about my Config struct).
Is hiding logic in a package like I am trying to do here a good idea in Go? If so, what's the "Go" way to pass this Config struct around?

In your main package you should specify
var conf configuration.Config = configuration.Load()
configuration refers to your imported package and Config is the exported struct (uppercase name) from that package. But you can also omit this, as the type can be inferred
var conf = configuration.Load()
As a side note: please don't use relative imports

in Go imports you always declare the full path of you package, dont use relative paths in imports, best example is that toml import import "github.com/BurntSushi/toml" that exist in:
GOPATH/src/github.com/BurntSushi/toml
GOPATH/pkg/_/github.com/BurntSushi/toml
Then build you package and main.go
package main
import "mypackage/configuration"
func main() {
// configuration contain all funcs & structs
var conf configuration.Config = configuration.Load()
}
Go it is not ruby.
Ref Packages: https://golang.org/doc/code.html

why don't you just import the configuration package and then do Go's variable declaration/instatiation shortcut? Maybe I'm missing something.
package main
import "mypackage/configuration"
func main() {
conf := configuration.Load()
}

Related

Get Name of Current Module in Go

I am attempting to create named loggers automatically for HTTP handlers that I'm writing, where I am passed a function (pointer).
I'm using the code mentioned in this question to get the name of a function:
package utils
import (
"reflect"
"runtime"
)
func GetFunctionName(fn interface{}) string {
value := reflect.ValueOf(fn)
ptr := value.Pointer()
ffp := runtime.FuncForPC(ptr)
return ffp.Name()
}
I'm using this in my main function to try it out like so:
package main
import (
"github.com/naftulikay/golang-webapp/experiments/functionname/long"
"github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path"
"github.com/naftulikay/golang-webapp/experiments/functionname/utils"
"log"
)
type Empty struct{}
func main() {
a := long.HandlerA
b := path.HandlerB
c := path.HandlerC
log.Printf("long.HandlerA: %s", utils.GetFunctionName(a))
log.Printf("long.nested.path.HandlerB: %s", utils.GetFunctionName(b))
log.Printf("long.nested.path.HandlerC: %s", utils.GetFunctionName(c))
}
I see output like this:
github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA
This is okay but I'd like an output such as long.HandlerA, long.nested.path.HandlerB, etc.
If I could get the Go module name (github.com/naftulikay/golang-webapp/experiments/functionname), I can then use strings.Replace to remove the module name to arrive at long/nested/path.HandlerB, then strings.Replace to replace / with . to finally get to my desired value, which is long.nested.path.HandlerB.
The first question is: can I do better than runtime.FuncForPC(reflect.ValueOf(fn).Pointer()) for getting the qualified path to a function?
If the answer is no, is there a way to get the current Go module name using runtime or reflect so that I can transform the output of runtime.FuncForPC into what I need?
Once again, I'm getting values like:
github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA
github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerB
github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerC
And I'd like to get values like:
long.HandlerA
long.nested.path.HandlerB
long.nested.path.HandlerC
EDIT: It appears that Go does not have a runtime representation of modules, and that's okay, if I can do it at compile time that would be fine too. I've seen the codegen documentation and I'm having a hard time figuring out how to write my own custom codegen that can be used from go generate.
The module info is included in the executable binary, and can be acquired using the debug.ReadBuildInfo() function (the only requirement is that the executable must be built using module support, but this is the default in the current version, and likely the only in future versions).
BuildInfo.Path is the current module's path.
Let's say you have the following go.mod file:
module example.com/foo
Example reading the build info:
bi, ok := debug.ReadBuildInfo()
if !ok {
log.Printf("Failed to read build info")
return
}
fmt.Println(bi.Main.Path)
// or
fmt.Println(bi.Path)
This will output (try it on the Go Playground):
example.com/foo
example.com/foo
See related: Golang - How to display modules version from inside of code
If your goal is to just have the name of the module available in your program, and if you are okay with setting this value at link time, then you may use the -ldflags build option.
You can get the name of the module with go list -m from within the module directory.
You can place everything in a Makefile or in a shell script:
MOD_NAME=$(go list -m)
go build -ldflags="-X 'main.MODNAME=$MOD_NAME'" -o main ./...
With main.go looking like:
package main
import "fmt"
var MODNAME string
func main() {
fmt.Println(MODNAME) // example.com
}
With the mentioned "golang.org/x/mod/modfile" package, an example might look like:
package main
import (
"fmt"
"golang.org/x/mod/modfile"
_ "embed"
)
//go:embed go.mod
var gomod []byte
func main() {
f, err := modfile.Parse("go.mod", gomod, nil)
if err != nil {
panic(err)
}
fmt.Println(f.Module.Mod.Path) // example.com
}
However embedding the entire go.mod file in your use case seems overkill. Of course you could also open the file at runtime, but that means you have to deploy go.mod along with your executable. Setting the module name with -ldflags is more straightforward IMO.

Reflect on struct type from reading a .go file

I'm discovering generator (go generate) and I'm trying to generate Validation function for my struct.
The idea is that I don't want my program to use reflect at runtime, I would rather have a generator use reflect to generate the actual method I want to use.
the problem is I can't import my structs in the generator code, the only way I found so far was to read the .go file from the generator and manually parse the types defined there using regex
I've got something like
models/models.go:
package models
//go:generate go run ../generator.go -file models.go
type MyStruct struct {
...
}
generator.go:
package main
func main() {
f, err := ioutil.ReadFile(fileName) // I read filename from the flag provided
...
// I parse f to generate my stuff
}
I would very much prefer to have an introspection package that would take a go code as a string and give me some information about the struct defined there
Or maybe there is a way to import the file that call go:generate to get directly access to the types
There is no need to specify file name , this code does the same :
//go:generate go run ../generator.go -file $GOFILE
With help of text/template package you are needless of parsing the file. A very simple example would be something like this. This will give you the clue :
package main
import (
"flag"
"os"
"text/template"
)
//go:generate go run main.go -name=A
//go:generate go run main.go -name=B
//go:generate go run main.go -name=C
var name = flag.String("name", "test", "name of struct")
var code = `
package main
type Struct{{.}} struct {}
func (s *Struct{{.}} ) Vailadte() bool {
return true
}
`
func main() {
flag.Parse()
file, _ := os.Create(*name + ".go")
defer file.Close()
tmpl, _ := template.New("test").Parse(code)
tmpl.Execute(file, *name)
}
Maybe you can utilize go/parser and go/ast in your generator.

Structuring local imports without GitHub in Golang

I'm building a simple app and after reading the doc on structuring go applications, I'm still confused.
I want this structure:
practice
models (packaged as models)
a
b
routers (packaged as routers)
a
b
app.go
Inside of app.go, I have the following:
package main
import (
"net/http"
// I have tried the following:
"practice/models/a"
"practice/models/b"
"practice/models"
"$GOPATH/practice/models/a"
"$GOPATH/practice/models/b"
"$GOPATH/practice/models"
...
)
func main() {
http.HandleFunc("/a", AHandler)
http.HandleFunc("/b", BHandler)
http.ListenAndServe(":8080", nil)
}
The A and B models look like this:
package models
import "net/http"
func AHandler(w http.ResponseWriter, r *http.Request) {
// code
}
Two questions:
What in the world is the right way to import these files? Do I really have to push them to github in order to be able to reference them? I understand the $GOPATH is the namespace for the entire go workspace on a local machine. My $GOPATH is set to include this directory.
Do I need to define a main method inside of these files? Can I just export a single function and have that be the handling function?
I have consulted the docs
See How to Write Go Code.
Use this directory structure:
- practice
- go.mod
- app.go
- models
- a.go
- b.go
- routers
- a.go
- b.go
where go.mod is created with the command go mod init practice where practice is the module path.
Import the packages as follows:
import (
"practice/routers"
"practice/models"
...
)
Use the imported packages like this:
func main() {
http.HandleFunc("/a", models.AHandler)
http.HandleFunc("/b", models.BHandler)
http.ListenAndServe(":8080", nil)
}
You do not need to push to github.com, even if you use github.com in the module path.
The main function in the main package is the entry point for the application. Do not define main functions in packages other than main.
What follows is the original answer based on GOPATH workspaces:
See How to Write Go Code.
Create your directory structure under $GOPATH/src.
$GOPATH
src
practice
models
routers
Import the packages as follows:
import (
"practice/routers"
"practice/models"
...
)
Use the imported packages like this:
func main() {
http.HandleFunc("/a", models.AHandler)
http.HandleFunc("/b", models.BHandler)
http.ListenAndServe(":8080", nil)
}
You do not need to push to github.com, even if you use 'github.com' in the file path.
The main function in the main package is the entry point for the application. Do not define main functions in packages other than main.
I think the other answer is out of date, you don't need to use GOPATH anymore.
Run:
go mod init yellow
Then create a file yellow.go:
package yellow
func Mix(s string) string {
return s + "Yellow"
}
Then create a file orange/orange.go:
package main
import "yellow"
func main() {
s := yellow.Mix("Red")
println(s)
}
Then build:
go build
https://golang.org/doc/code.html

Is it possible don't specify package name?

Here is an example of my code:
package main
import (
"./bio"
)
func main() {
bio.PeptideEncoding(genome, codonTable)
}
Is it possible to use functions from my paxkage (bio) without specifying package name:
func main() {
PeptideEncoding(genome, codonTable)
}
?
You could use as an import declaration like:
. "./bio"
If an explicit period (.) appears instead of a name, all the package's exported identifiers declared in that package's package block will be declared in the importing source file's file block and must be accessed without a qualifier.
That is what a testing framework like govey does:
package package_name
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestIntegerStuff(t *testing.T) {
Convey("Given some integer with a starting value", t, func() {
x := 1
Convey("When the integer is incremented", func() {
x++
Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 2)
})
})
})
}
You don't need to use convey.So(), or convey.Convey() because of the import starting with a '.'.
Don't abuse it though, since, as twotwotwo comments, The style guide discourages it outside of tests.
Except for this one case, do not use import . in your programs.
It makes the programs much harder to read because it is unclear whether a name like Quux is a top-level identifier in the current package or in an imported package.
That is why I mentioned a testing framework using this technique.
As commented by Simon Whitehead, using relative import is not generally considered as the best practice (see for instance "Go language package structure").
You should also import the package via the GOPATH instead of relatively, as show in "Import and not used error".

Package selection in Go

I'm trying to write an application to pull status from a database, but I seem to be getting stuck on a really basic principle of the language. I have the program written, but it doesn't compile due to the error use of package time not in selector.
A really basic example (from play.golang.org's own test environment)
package main
import (
"fmt"
"time"
)
func main() {
s_str := time.Now()
fmt.Println( printT(s_str) )
}
func printT(t time) time {
return t.Add(100)
}
Unfortunately, I've found documentation and helpdocs online a bit wanting. My understanding is that the import statement should include the library for the entire program like in C++ correct?
You have to prefix the imported types or variables with the name you gave to the package in the import (here you use the default name, that is "time"). That's what you did for the function Now but you have to do it also for the types.
So the type isn't time but time.Time (that is : the type Time that is declared in the package you import with the name "time").
Change your function to
func printT(t time.Time) time.Time {
return t.Add(100)
}
And for your second question : No, the import statement doesn't include the library for the entire program but only for the current file.

Resources