我正在学习Go,但我觉得在编译时,我不应该留下任何未使用的变量或包,这有点烦人。

这真的让我慢下来了。例如,我只想声明一个新包并计划稍后使用它,或者只是取消某些命令的注释以进行测试。我总是得到错误,需要注释所有这些使用。

在围棋中,有什么方法可以避免这种检查吗?


这个错误迫使您编写更好的代码,并确保使用您声明或导入的所有内容。它使阅读其他人编写的代码更容易(您总是可以确定所有声明的变量都将被使用),并避免一些可能的死代码。

但是,如果你真的想跳过这个错误,你可以使用空白标识符(_):

package main

import (
    "fmt" // imported and not used: "fmt"
)

func main() {
    i := 1 // i declared and not used
}

就变成了

package main

import (
    _ "fmt" // no more error
)

func main() {
    i := 1 // no more error
    _ = i
}

正如kostix在下面的评论中所说,你可以在FAQ中找到Go团队的官方立场:

未使用变量的存在可能表明存在错误,而未使用的导入只会降低编译速度。在代码树中积累足够多的未使用的导入,事情就会变得非常缓慢。由于这些原因,围棋两者都不允许。


你可以使用一个简单的“null函数”,例如:

func Use(vals ...interface{}) {
    for _, val := range vals {
        _ = val
    }
}

你可以这样使用:

package main

func main() {
    a := "declared and not used"
    b := "another declared and not used"
    c := 123

    Use(a, b, c)
}

这里还有一个包,所以你不必每次都定义Use函数:

import (
  "github.com/lunux2008/xulu"
)

func main() {
  // [..]

  xulu.Use(a, b, c)
}

根据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.

我并不一定同意这个观点,原因有很多,不值得细讲。它就是这样,而且在不久的将来不太可能改变。

对于包,有goimports工具可以自动添加丢失的包并删除不使用的包。例如:

# Install it
$ go get golang.org/x/tools/cmd/goimports

# -w to write the source file instead of stdout
$ goimports -w my_file.go

你应该能够从任何一个不错的编辑器运行这个,例如Vim:

:!goimports -w %

goimports页面列出了用于其他编辑器的一些命令,您通常将其设置为在将缓冲区保存到磁盘时自动运行。

注意,goimports也会运行gofmt。


如前所述,对于变量,最简单的方法是(临时)将它们赋值给_:

// No errors
tasty := "ice cream"
horrible := "marmite"

// Commented out for debugging
//eat(tasty, horrible)

_, _ = tasty, horrible

目前还没有提到的一个角度是用于编辑代码的工具集。

使用Visual Studio代码和lukehoban的名为Go的扩展将为你做一些自动魔术。Go扩展自动运行gofmt, golint等,并删除和添加导入项。所以至少这部分是自动的。

我承认这不是问题的100%解决方案,但无论如何足够有用。


如果其他人很难理解这一点,我认为用非常直接的术语解释它可能会有所帮助。如果你有一个你不使用的变量,例如你已经注释掉调用的函数(一个常见的用例):

myFn := func () { }
// myFn()

你可以给函数赋值一个无用的/空白的变量,这样它就不再被使用了:

myFn := func () { }
_ = myFn
// myFn()

当我想在处理代码的另一部分时暂时禁用发送电子邮件时,我遇到了这个问题。

注释服务的使用会触发很多级联错误,所以我使用了一个条件而不是注释

if false {
    // Technically, svc still be used so no yelling
    _, err = svc.SendRawEmail(input) 
    Check(err)
}

2年前我在学习围棋的时候遇到了这个问题,所以我声明了自己的函数。

// UNUSED allows unused variables to be included in Go programs
func UNUSED(x ...interface{}) {}

然后你可以这样使用它:

UNUSED(x)
UNUSED(x, y)
UNUSED(x, y, z)

它最大的好处是,你可以把任何东西都变成闲置的。

它比下面的好吗?

_, _, _ = x, y, z

这取决于你。


据我所知,Go编译器中的这些行看起来像注释掉的那些行。您应该能够构建自己的工具链,忽略这些适得其反的警告。


我的答案是黑进该死的信息源。这个补丁适用于1.19.4,然后你用-gcflags all=-nounusederrors构建源代码(你需要在其他地方帮助才能从源代码构建Golang):

tty/tty.go:98:6: hello declared but not used, 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


免责声明:这不是一个全面的补丁-它只影响运行go构建或编译工具。当使用ast(树解析器),跟踪和其他一些工具时仍然应该有错误,因为我没有混乱的内部/类型,只有cmd/compile/internal/types2 - Golang源代码有点混乱。我希望他们能在某个时候进行重构,去掉这些冗余。