How to avoid annoying error "declared and not used" - go

I'm learning Go but I feel it is a bit annoying that when compiling, I should not leave any variable or package unused.
This is really quite slowing me down. For example, I just wanted to declare a new package and plan to use it later or just uncomment some command to test. I always get the error and need to go comment all of those uses.
Is there any way to avoid this kind of check in Go?

That error is here to force you to write better code, and be sure to use everything you declare or import. It makes it easier to read code written by other people (you are always sure that all declared variables will be used), and avoid some possible dead code.
But, if you really want to skip this error, you can use the blank identifier (_) :
package main
import (
"fmt" // imported and not used: "fmt"
)
func main() {
i := 1 // i declared and not used
}
becomes
package main
import (
_ "fmt" // no more error
)
func main() {
i := 1 // no more error
_ = i
}
As said by kostix in the comments below, you can find the official position of the Go team in the FAQ:
The presence of an unused variable may indicate a bug, while unused imports just slow down compilation. Accumulate enough unused imports in your code tree and things can get very slow. For these reasons, Go allows neither.

You can use a simple "null function" for this, for example:
func Use(vals ...interface{}) {
for _, val := range vals {
_ = val
}
}
Which you can use like so:
package main
func main() {
a := "declared and not used"
b := "another declared and not used"
c := 123
Use(a, b, c)
}
There's also a package for this so you don't have to define the Use function every time:
import (
"github.com/lunux2008/xulu"
)
func main() {
// [..]
xulu.Use(a, b, c)
}

I ran into this while I was learning Go 2 years ago, so I declared my own function.
// UNUSED allows unused variables to be included in Go programs
func UNUSED(x ...interface{}) {}
And then you can use it like so:
UNUSED(x)
UNUSED(x, y)
UNUSED(x, y, z)
The great thing about it is, you can pass anything into UNUSED.
Is it better than the following?
_, _, _ = x, y, z
That's up to you.

According to the FAQ:
Some have asked for a compiler option to turn those checks off or at least reduce them to warnings. Such an option has not been added, though, because compiler options should not affect the semantics of the language and because the Go compiler does not report warnings, only errors that prevent compilation.
There are two reasons for having no warnings. First, if it's worth complaining about, it's worth fixing in the code. (And if it's not worth fixing, it's not worth mentioning.) Second, having the compiler generate warnings encourages the implementation to warn about weak cases that can make compilation noisy, masking real errors that should be fixed.
I don't necessarily agree with this for various reasons not worth going into. It is what it is, and it's not likely to change in the near future.
For packages, there's the goimports tool which automatically adds missing packages and removes unused ones. For example:
# Install it
$ go get golang.org/x/tools/cmd/goimports
# -w to write the source file instead of stdout
$ goimports -w my_file.go
You should be able to run this from any half-way decent editor − for example for Vim:
:!goimports -w %
The goimports page lists some commands for other editors, and you typically set it to be run automatically when you save the buffer to disk.
Note that goimports will also run gofmt.
As was already mentioned, for variables the easiest way is to (temporarily) assign them to _ :
// No errors
tasty := "ice cream"
horrible := "marmite"
// Commented out for debugging
//eat(tasty, horrible)
_, _ = tasty, horrible

In case others have a hard time making sense of this, I think it might help to explain it in very straightforward terms. If you have a variable that you don't use, for example a function for which you've commented out the invocation (a common use-case):
myFn := func () { }
// myFn()
You can assign a useless/blank variable to the function so that it's no longer unused:
myFn := func () { }
_ = myFn
// myFn()

One angle not so far mentioned is tool sets used for editing the code.
Using Visual Studio Code along with the Extension from lukehoban called Go will do some auto-magic for you. The Go extension automatically runs gofmt, golint etc, and removes and adds import entries. So at least that part is now automatic.
I will admit its not 100% of the solution to the question, but however useful enough.

As far as I can tell, these lines in the Go compiler look like the ones to comment out. You should be able to build your own toolchain that ignores these counterproductive warnings.

I ran into this issue when I wanted to temporarily disable the sending of an email while working on another part of the code.
Commenting the use of the service triggered a lot of cascade errors, so instead of commenting I used a condition
if false {
// Technically, svc still be used so no yelling
_, err = svc.SendRawEmail(input)
Check(err)
}

My answer is to hack the f-ing sources. This patch works against 1.19.4 and then you build your sources with -gcflags all=-nounusederrors (you'll need help elsewhere in order to build Golang from sources):
tty/tty.go:98:6: hello declared but not used, but nobody cares
It doesn't suppress some unused labels and the grammar of the appended message is a bit sloppy, but nobody cares.
From 6eb19713fb5302ef2d5eb4af0c05e86c88d055c7 Mon Sep 17 00:00:00 2001
From: Daniel Santos <daniel.santos#pobox.com>
Date: Mon, 9 Jan 2023 21:56:03 -0600
Subject: Add -nounusedwarnings
---
src/cmd/compile/internal/base/flag.go | 1 +
src/cmd/compile/internal/types2/errors.go | 10 ++++++++++
src/cmd/compile/internal/types2/labels.go | 2 +-
src/cmd/compile/internal/types2/resolver.go | 8 ++++----
src/cmd/compile/internal/types2/stmt.go | 4 ++--
src/cmd/go/alldocs.go | 2 ++
src/cmd/go/internal/work/build.go | 2 ++
src/go/types/gotype.go | 3 +++
8 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go
index a363b83984..f295746f64 100644
--- a/src/cmd/compile/internal/base/flag.go
+++ b/src/cmd/compile/internal/base/flag.go
## -111,6 +111,7 ## type CmdFlags struct {
MemProfileRate int "help:\"set runtime.MemProfileRate to `rate`\""
MutexProfile string "help:\"write mutex profile to `file`\""
NoLocalImports bool "help:\"reject local (relative) imports\""
+ NoUnusedErrors bool "help:\"no errors for unused imports and variables\""
Pack bool "help:\"write to file.a instead of file.o\""
Race bool "help:\"enable race detector\""
Shared *bool "help:\"generate code that can be linked into a shared library\"" // &Ctxt.Flag_shared, set below
diff --git a/src/cmd/compile/internal/types2/errors.go b/src/cmd/compile/internal/types2/errors.go
index 2a3e88a2fe..0405fa26de 100644
--- a/src/cmd/compile/internal/types2/errors.go
+++ b/src/cmd/compile/internal/types2/errors.go
## -8,6 +8,7 ## package types2
import (
"bytes"
+ "cmd/compile/internal/base"
"cmd/compile/internal/syntax"
"fmt"
"runtime"
## -275,6 +276,15 ## func (check *Checker) softErrorf(at poser, format string, args ...interface{}) {
check.err(at, check.sprintf(format, args...), true)
}
+func (check *Checker) unusedf(at poser, format string, args ...interface{}) {
+ if base.Flag.NoUnusedErrors {
+ pos := posFor(at)
+ fmt.Printf("%s: %s, but nobody cares\n", pos, check.sprintf(format, args...))
+ } else {
+ check.softErrorf(at, format, args)
+ }
+}
+
func (check *Checker) versionErrorf(at poser, goVersion string, format string, args ...interface{}) {
msg := check.sprintf(format, args...)
if check.conf.CompilerErrorMessages {
diff --git a/src/cmd/compile/internal/types2/labels.go b/src/cmd/compile/internal/types2/labels.go
index 6f02e2fc96..d3ae602549 100644
--- a/src/cmd/compile/internal/types2/labels.go
+++ b/src/cmd/compile/internal/types2/labels.go
## -35,7 +35,7 ## func (check *Checker) labels(body *syntax.BlockStmt) {
for name, obj := range all.elems {
obj = resolve(name, obj)
if lbl := obj.(*Label); !lbl.used {
- check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name)
+ check.unusedf(lbl.pos, "label %s declared but not used", lbl.name)
}
}
}
diff --git a/src/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go
index 5d498b6b2b..935435b03f 100644
--- a/src/cmd/compile/internal/types2/resolver.go
+++ b/src/cmd/compile/internal/types2/resolver.go
## -731,15 +731,15 ## func (check *Checker) errorUnusedPkg(obj *PkgName) {
}
if obj.name == "" || obj.name == "." || obj.name == elem {
if check.conf.CompilerErrorMessages {
- check.softErrorf(obj, "imported and not used: %q", path)
+ check.unusedf(obj, "imported and not used: %q", path)
} else {
- check.softErrorf(obj, "%q imported but not used", path)
+ check.unusedf(obj, "%q imported but not used", path)
}
} else {
if check.conf.CompilerErrorMessages {
- check.softErrorf(obj, "imported and not used: %q as %s", path, obj.name)
+ check.unusedf(obj, "imported and not used: %q as %s", path, obj.name)
} else {
- check.softErrorf(obj, "%q imported but not used as %s", path, obj.name)
+ check.unusedf(obj, "%q imported but not used as %s", path, obj.name)
}
}
}
diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go
index 74d4164ba9..c4255e4413 100644
--- a/src/cmd/compile/internal/types2/stmt.go
+++ b/src/cmd/compile/internal/types2/stmt.go
## -66,7 +66,7 ## func (check *Checker) usage(scope *Scope) {
return unused[i].pos.Cmp(unused[j].pos) < 0
})
for _, v := range unused {
- check.softErrorf(v.pos, "%s declared but not used", v.name)
+ check.unusedf(v.pos, "%s declared but not used", v.name)
}
for _, scope := range scope.children {
## -804,7 +804,7 ## func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
v.used = true // avoid usage error when checking entire function
}
if !used {
- check.softErrorf(lhs, "%s declared but not used", lhs.Value)
+ check.unusedf(lhs, "%s declared but not used", lhs.Value)
}
}
}
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index a3c1fecb91..1f4c5c7b5c 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
## -179,6 +179,8 ##
// directory, but it is not accessed. When -modfile is specified, an
// alternate go.sum file is also used: its path is derived from the
// -modfile flag by trimming the ".mod" extension and appending ".sum".
+// -nounusederrors
+// do not error on unused functions, imports, variables, etc.
// -overlay file
// read a JSON config file that provides an overlay for build operations.
// The file is a JSON struct with a single field, named 'Replace', that
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index 5f11cdabaf..b37f1c8a01 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
## -135,6 +135,8 ## and test commands:
directory, but it is not accessed. When -modfile is specified, an
alternate go.sum file is also used: its path is derived from the
-modfile flag by trimming the ".mod" extension and appending ".sum".
+ -nounusederrors
+ do not error on unused functions, imports, variables, etc.
-overlay file
read a JSON config file that provides an overlay for build operations.
The file is a JSON struct with a single field, named 'Replace', that
diff --git a/src/go/types/gotype.go b/src/go/types/gotype.go
index e8ff9658da..5a60b83346 100644
--- a/src/go/types/gotype.go
+++ b/src/go/types/gotype.go
## -47,6 +47,8 ## The flags are:
verbose mode
-c
compiler used for installed packages (gc, gccgo, or source); default: source
+ -nounusederrors
+ treat "unused" errors as warnings
Flags controlling additional output:
## -104,6 +106,7 ## var (
allErrors = flag.Bool("e", false, "report all errors, not just the first 10")
verbose = flag.Bool("v", false, "verbose mode")
compiler = flag.String("c", "source", "compiler used for installed packages (gc, gccgo, or source)")
+ nounusederr= flag.Bool("nounusederrors", false, "treat unused objects as warnings")
// additional output control
printAST = flag.Bool("ast", false, "print AST")
--
2.38.2
Disclaimer: this isn't a comprehensive patch -- it only affects running go build or the compile tool. Should still have errors when using ast (tree parser), tracing and a few others tools because I didn't mess with internal/types, only cmd/compile/internal/types2 -- the Golang sources are a bit messy like that. I'm hoping they can refactor at some point and get rid of these redundancies.

Related

Issues with go:embed directive: Invalid name

I want to serve music from my HDD. All audio files (mp3/flac/etc.) are read-only. so using //go:embed should work for those files. So I have
//go:embed "assets/Media/Music/*"
var f embed.FS
func main() {
router := gin.Default()
router.StaticFS("/public", http.FS(f))
router.GET("/", func(c *gin.Context) {
file, _ := f.ReadFile("assets/Media/Music/<some_other_folders>/01 - my_song.mp3")
c.Data(
http.StatusOK,
"audio/mpeg",
file,
)
}
router.Run(":8080")
}
The problem is that the Go compiler complains that certain folder names are invalid for example: 'Big K.R.I.T' and 'Al B. Sure!' The docs say that file names shouldn't have spaces or '.' but I'm unaware if that applies if I don't explicitly state the file/folder name.
My question is
What constitutes a "invalid name"
Is there a way to allow folder names with spaces for the embed directive
EDIT
The code above is (almost) exactly what I'm running locally. When I try to build (or go run main.go) VS Code catches this error. The ending is a folder name and not a file for clarification. Hopefully this helps. Thanks in advance
Consulting the go command src where the invalid name error is emitted:
if elem := filepath.Base(dir); isBadEmbedName(elem) {
if dir == file {
return nil, nil, fmt.Errorf("cannot embed %s %s: invalid name %s", what, rel, elem)
} else {
return nil, nil, fmt.Errorf("cannot embed %s %s: in invalid directory %s", what, rel, elem)
}
}
This references isBadEmbedName which then relies on the function module.CheckFilePath. The docs for this function states:
// CheckFilePath checks that a slash-separated file path is valid.
// The definition of a valid file path is the same as the definition
// of a valid import path except that the set of allowed characters is larger:
// all Unicode letters, ASCII digits, the ASCII space character (U+0020),
// and the ASCII punctuation characters
// “!#$%&()+,-.=#[]^_{}~”.
// (The excluded punctuation characters, " * < > ? ` ' | / \ and :,
// have special meanings in certain shells or operating systems.)
//
// CheckFilePath may be less restrictive in the future, but see the
// top-level package documentation for additional information about
// subtleties of Unicode.
So it would appear - based on the last line - that the embedded filename rules is a moving target.
Running some tests against this validation function, show even more subtleties e.g. dot.in.middle vs dot.at.end.:
https://play.golang.org/p/7x6i_Aj8eEJ
so this would explain why your path Big K.R.I.T. fails.
Also worthy of note: when using the //go:embed directive with a wildcard or not affects compilation success.
As you experienced, your compilation failed when using:
//go:embed "assets/Media/Music/*"
var f embed.FS
due to the invalid filename format in that directory. However, if you drop the wildcard:
//go:embed "assets/Media/Music"
var f embed.FS
compilation will succeed - but any invalid filenames will just be silently removed from the embed manifest.
If you want to inspect explicitly what files will be embedded in your program, you can avail of go list e.g.
go list -json
and look for the EmbedFiles section of the output. Or use jq to zero-in on this section:
$ go list -json | jq .EmbedFiles
[
"Static Files/Space Dir/Big K.R.I.T.mp3",
"Static Files/Space Dir/world.txt",
"Static Files/hey!"
]

error: "declared and not used" even if I assign to it

I can't find why below code gives compile error "alive declared and not used".
func ping(ip string) {
var alive bool
_, err := exec.Command("ping", "-n 1", "-w 1000", ip).Output()
if err != nil {
alive = false
} else {
alive = true
}
}
The compile error you're seeing is exactly what is happening. That var alive bool is unused. You declare it and assign a value to it but you never do anything with it.
Here is a playground-friendly modification of your code that will run:
package main
import (
"fmt"
"strconv"
)
func main() {
fmt.Println(isInt("Hello, playground")) // prints false
fmt.Println(isInt("1234567890")) // prints true
}
func isInt(s string) bool {
var alive bool
_, err := strconv.Atoi(s) // simply to demonstrate an error case
if err != nil {
alive = false
} else {
alive = true
}
return alive
}
Notice that I return alive. The function is useless and not something I would suggest in and of itself but it should help illustrate what is missing in your example.
Since it is a local variable, it will exit scope at the end of the function. alive is neither evaluated nor returned inside the function. Hence the compiler complains.
Those strange limitations in Golang Devs team heads are terribly annoying.
Why not to allow people to disable their limitations with options?
Answer is simple: they write the lang for themselves (for guugle), not for the community.
Fortunately, Go is OpenSource and even written in Go.
So, here is a simple patch removing the "declared and not used" or "declared but not used" errors raising:
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index 770210f..78c0cbc 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
## -49,10 +49,7 ## func walk(fn *Node) {
if defn.Left.Name.Used() {
continue
}
- yyerrorl(defn.Left.Pos, "%v declared and not used", ln.Sym)
defn.Left.Name.SetUsed(true) // suppress repeats
- } else {
- yyerrorl(ln.Pos, "%v declared and not used", ln.Sym)
}
}
diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go
index abd9d05..8b15786 100644
--- a/src/go/types/stmt.go
+++ b/src/go/types/stmt.go
## -55,6 +55,7 ## func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body
}
func (check *Checker) usage(scope *Scope) {
+ return
var unused []*Var
for _, elem := range scope.elems {
if v, _ := elem.(*Var); v != nil && !v.used {
(Actual for go1.12)
Unpack a fresh Golang into /usr/local/go and apply the patch.
Then compile:
export GOROOT_BOOTSTRAP=/usr/local/go2
cp -a /usr/local/go /usr/local/go2
cd /usr/local/go/src
sed -e 's#^bash run.bash.*##' -i all.bash
./all.bash
rm -rf /usr/local/go2
unset GOROOT_BOOTSTRAP
It's much faster to apply the patch once [per new version] than every time deal with every missed variable.

Function not behaving properly when imported from an external file but fine when in the same file?

I'm working on Project Euler questions in order to get used to Go. The question is not about Project Euler, but there is Project Euler specific code in this question that might give away the challenge of a question. "Spoiler Alert" or whatever, but now you know. Here's my file structure:
+ Project Euler
+-+ Go <= GOPATH set here
+-+ src
+-+ util
| +- util.go
|
+- 001.go
+- 002.go
...
+- 023.go
For problem 23, I'm adding a new function SumOfDivisors to util.go (a file with various methods used by multiple problems):
func GetPrimeFactors(val int) map[int]int {
primes := map[int]int{}
init := val
num := 2
for val > 1 {
if (val % num) == 0 {
if num == init {
return nil
}
_, e := primes[num]
if e {
primes[num]++
} else {
primes[num] = 1
}
val /= num
} else {
num++
}
}
return primes
}
func SumOfDivisors(val int) int {
primes := GetPrimeFactors(val)
if primes == nil {
if val == 0 {
return 0
} else {
return 1
}
}
total := 1
for k, v := range primes {
if v > 1 {
n := int((math.Pow(float64(k), float64(v+1)) - 1) / float64(k-1))
total *= n
} else {
n := k + 1
total *= n
}
}
return total - val
}
In order to test this method, I wrote this basic Go inside 023.go:
package main
import (
"fmt"
"util"
)
func main() {
fmt.Println(util.SumOfDivisors(12))
}
I have my GOPATH set to /Project Euler/Go and it builds and runs seemingly fine when I call go run 023.go. "Seemingly fine" meaning there are no errors, warnings, no output other than my code.
What is printing to the screen is 1 when it should be 16. I don't think it's a logic issue because when I copy the function from my util.go into 023.go (and fix the call to GetPrimeFactors to be util.GetPrimeFactors) then the function runs just fine and prints 16 just like it should. I've tried adding fmt.Println("TEST") to util.SumOfDivisors but it won't print those statements out and I don't get errors or anything else. If I change the name of the function in util.go to anything else, even if the main function is 023.go doesn't change, it still builds and runs outputting 1. It's really behaving weird.
Other functions in my util.go file seem to be called just fine.
I'm running Go 1.4.2. What might be causing this kind of behavior? The function works properly locally but not when moved to an external file that's imported and why can't that external file print anything to screen? All this while building just fine.
Use go build -a 023.go
This will rebuild all dependencies that 023.go has and will avoid using old compiled versions of a package. This is one of go's strong suits that enables faster build times but it can cause these types of problems too.
As I mentioned in my comment, you kept building 023.go but you probably didn't run go build util.go to update the util package that 023.go depended on.
The -a option will rebuild all dependencies and you can even add -v to see what it is building and when.
go build -a -v 023.go
OR
go run -a -v 023.go
Run, build, clean, and test have similar flags. Run go help build for more info.
After a little more toying around, I've found that there exists a /Project Euler/pkg folder with a util.a inside it. Apparently a version of my code was built and it's intermediate files were cached.
After deleting the pkg folder, everything fell into place. The mismatched function names became a compiler error, then (after correcting the function names) my util.go fmt.Println calls were starting to print, and my answer was coming out as 16. In the end this wasn't a code solution, it was a caching problem.

Golang Flag gets interpreted as first os.Args argument

I would like to run my program like this:
go run launch.go http://example.com --m=2 --strat=par
"http://example.com" gets interpreted as the first command line argument, which is ok, but the flags are not parsed after that and stay at the default value. If I put it like this:
go run launch.go --m=2 --strat=par http://example.com
then "--m=2" is interpreted as the first argument (which should be the URL).
I could also just remove the os.Args completely, but then I would have only optional flags and I want one (the URL) to be mandatory.
Here's my code:
package main
import (
"fmt"
"webcrawler/crawler"
"webcrawler/model"
"webcrawler/urlutils"
"os"
"flag"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Url must be provided as first argument")
}
strategy := flag.String("strat", "par", "par for parallel OR seq for sequential crawling strategy")
routineMultiplier := flag.Int("m", 1, "Goroutine multiplier. Default 1x logical CPUs. Only works in parallel strategy")
page := model.NewBasePage(os.Args[1])
urlutils.BASE_URL = os.Args[1]
flag.Parse()
pages := crawler.Crawl(&page, *strategy, *routineMultiplier)
fmt.Printf("Crawled: %d\n", len(pages))
}
I am pretty sure that this should be possible, but I can't figure out how.
EDIT:
Thanks justinas for the hint with the flag.Args(). I now adapted it like this and it works:
...
flag.Parse()
args := flag.Args()
if len(args) != 1 {
log.Fatal("Only one argument (URL) allowed.")
}
page := model.NewBasePage(args[0])
...
os.Args doesn't really know anything about the flag package and contains all command-line arguments. Try flag.Args() (after calling flag.Parse(), of course).
As a followup, to parse flags that follow a command like
runme init -m thisis
You can create your own flagset to skip the first value like
var myValue string
mySet := flag.NewFlagSet("",flag.ExitOnError)
mySet.StringVar(&myValue,"m","mmmmm","something")
mySet.Parse(os.Args[2:])
This tripped me up too, and since I call flag.String/flag.Int64/etc in a couple of places in my app, I didn't want to have to pass around a new flag.FlagSet all over the place.
// If a commandline app works like this: ./app subcommand -flag -flag2
// `flag.Parse` won't parse anything after `subcommand`.
// To still be able to use `flag.String/flag.Int64` etc without creating
// a new `flag.FlagSet`, we need this hack to find the first arg that has a dash
// so we know when to start parsing
firstArgWithDash := 1
for i := 1; i < len(os.Args); i++ {
firstArgWithDash = i
if len(os.Args[i]) > 0 && os.Args[i][0] == '-' {
break
}
}
flag.CommandLine.Parse(os.Args[firstArgWithDash:])
The reason I went with this is because flag.Parse just calls flag.CommandLine.Parse(os.Args[1:]) under the hood anyway.
You can check if the Arg starts with "--" or "-" and avoid using that Arg in a loop.
For example:
for _, file := range os.Args[1:] {
if strings.HasPrefix(file, "--") {
continue
}
//do stuff
}

Constants in Go established during init

In my Go program, there are configuration values that I want to be constant for the duration of program execution, but that I want to be able to change at the deployment site. As far as I can tell, there's no way to achieve this with the const keyword, since (again, as far as I can tell) its value must be a constant specified at compile time. This means that the only way to achieve what I want would be to declare normal variables and initialize them during the package's init function. It's not that that won't work, but rather that there will now be nothing to prevent these pseudo-constant's values from changing.
My two questions are:
Am I missing something about how const works?
Assuming I'm not, what's the preferred way to handle this? A public function that returns a private variable that I never expose, never changing it? Just hoping people don't alter the variables, since they're really configuration settings?
Create a file "config.go" and create the vars you want to expose.
Don't export them (make them all lower case). Instead create public (upper case) funcs that give access to each item.
package config
var x = 10
func X() int {
return x
}
When you want those variables you simply import ./config and use them in code as follows:
if config.X()
Obviously, you can set the variables in the package init.
The following code is almost the same as the second method of #Christopher, except that it is not a module, it located in the main package.
package main
import (
"os"
)
type Config struct {
debug bool
key string
proxyNumber int
}
func (c *Config) Debug() bool {
return c.debug
}
func (c *Config) Key() string {
return c.key
}
func (c *Config) ProxyNumber() int {
return c.proxyNumber
}
const (
CONFIG_NAME = "config.ini"
)
var config *Config
func init() {
DefaultConfig()
if Exists(CONFIG_NAME) {
//try to save the config file
}else {
//try to load from the config file
}
}
func DefaultConfig() {
config = &Config{debug:true, key:"abcde",
proxyNumber:5,
}
}
//Exist: check the file exist
func Exists(path string) bool {
_, err := os.Stat(path)
if err == nil { return true }
if os.IsNotExist(err) { return false }
return false
}
you can use config to load and save the config file.
This is a very good question because it delves into what I suspect may be an omission from Go - immutable state.
From the language reference, "constant expressions may contain only constant operands and are evaluated at compile-time."
You cannot make vars constant, which is a shame. Joe's answer proposes encapsulation as a solution, which will work well - but it's verbose, tedious and might introduce errors.
By comparison, many impure functional languages combine mutable variables with single-assignment immutable values. For example, Scala has the keywords 'val' and 'var'; the meaning of Scala's 'var' is quite similar to Go's 'var'. Immutability is a useful tool in the toolbox because referentially-transparent side-effect-free functions can be written, alongside stateful mutable procedural code. Both have their place. Immutability is also an valuable tool for concurrency because there is no worry about possible race conditions if immutable values are shared between goroutines.
So in my opinion, amongst its many strengths, this is one of Go's shortcomings. It would presumably not be hard to support vals as well as vars, the difference being that the compiler checks that each val is assigned exactly once.
Until that feature is added, you have encapsulation as your only option.
You can do something like this:
package main
import (
"fmt"
"strconv"
)
var a string
func main() {
myvar, err := strconv.Atoi(a)
if err != nil {
fmt.Println(err)
}
fmt.Println(myvar)
}
and compile the program with
go build -ldflags '-X main.a 10' test.go
That way you can define a constant during compile time.
Just use standard go flags with iniflags. Standard go flags allow setting arbitrary config variables at program start via passing command-line flags, while iniflags "magically" add support for reading config variables from ini files.
You can wrap the variable in a function that returns its value:
func genConst(x int) func() int {
return func() int {
return x
}
}
var Constx = genConst(5)
var x1 = Constx()
This way the value cannot be changed by accident, even in the file where it's defined

Resources