返回错误,参数过多。

huangapple go评论80阅读模式
英文:

Too many arguments to return error

问题

我一直在为什么这段代码会抛出错误而苦恼:

package util

import (
	"path/filepath"
	"sync"

	"github.com/go-ini/ini"
)

// ConfigMap is map for config values
type ConfigMap struct {
	LogPath         string
	PublicDir       string
	SessionName     string
	Debug           bool
	DBUsersHost     string
	DBUsersName     string
	DBUsersUsername string
	DBUsersPassword string
}

var once sync.Once

// Config loads and return config object
func Config() (*ConfigMap, error) {
	once.Do(func() {
		// Find the location of the app.conf file
		configFilePath, err := filepath.Abs("../build/app.conf")
		if err != nil {
			return nil, err
		}

		// Load app.conf
		cfg, err := ini.Load(configFilePath)
		if err != nil {
			return nil, err
		}

		// Get app mode
		mode, err := AppMode()
		if err != nil {
			return nil, err
		}

		c := &ConfigMap{}
		err = cfg.Section(mode).MapTo(c)
		if err != nil {
			return nil, err
		}

		return c, err
	})
}

正如你所看到的,配对是完全正确的。每个返回代码都返回&ConfigMaperr,函数签名也与之匹配。我漏掉了什么?

英文:

I've been pulling my hair out as to why this code throws the error:

package util

import (
   "path/filepath"
   "sync"

   "github.com/go-ini/ini"
)

// ConfigMap is map for config values
type ConfigMap struct {
    LogPath         string
    PublicDir       string
    SessionName     string
    Debug           bool
    DBUsersHost     string
    DBUsersName     string
    DBUsersUsername string
    DBUsersPassword string
}

var once sync.Once

// Config loads and return config object
func Config() (*ConfigMap, error) {
    once.Do(func() {
	    // Find the location of the app.conf file
	    configFilePath, err := filepath.Abs("../build/app.conf")
	    if err != nil {
		    return nil, err
	    }

	    // Load app.conf
	    cfg, err := ini.Load(configFilePath)
	    if err != nil {
		    return nil, err
	    }

	    // Get app mode
	    mode, err := AppMode()
	    if err != nil {
		    return nil, err
	    }

	    c := &ConfigMap{}
	    err = cfg.Section(mode).MapTo(c)
	    if err != nil {
		    return nil, err
	    }

	    return c, err
    })
}

As you can see, the pairing is exactly correct. Each return code returns &ConfigMap and err and the function signature matches it. What am I missing?

答案1

得分: 8

你将一个匿名函数值传递给了once.Do()(即Once.Do()),而return语句位于其中。这意味着这些return语句想要从匿名函数中返回,但它没有任何返回值:

func Config() (*ConfigMap, error) {
    once.Do(func() {
        // 这里不能返回任何值,只能这样写:
        return
    })

    // 在这里你需要返回一些东西:
    return &ConfigMap{}, nil
}

你可以创建与Config()返回值匹配的全局变量,匿名函数应该将这些值存储在其中。在Config()中,你可以返回这些全局变量的值。

var cm *ConfigMap
var cmErr error

func Config() (*ConfigMap, error) {
    once.Do(func() {
        // 加载配置,并存储,例如:
        cm, cmErr = &ConfigMap{}, nil
    })

    return cm, cmErr
}

我们真的需要全局变量吗?由于Config()返回的值是由传递给once.Do()的匿名函数产生的,而该函数保证只运行一次,所以是的,你需要将它们存储在某个地方,以便能够多次返回它们,即使匿名函数不再被调用/运行(在对Config()的后续调用中)。

问题:这里可能存在数据竞争吗?

如果从多个goroutine中调用Config(),至少有一个goroutine将写入全局变量cmcmErr,而所有goroutine都将读取它们。因此,问这个问题是合理的。

但是答案是否定的,上述代码是安全的。全局变量cmcmErr只会被写入一次,并且这发生在它们被读取之前。因为once.Do()会阻塞,直到匿名函数返回。如果Config()(因此是once.Do())同时从多个goroutine中调用,所有goroutine都将阻塞,直到匿名函数完成(仅一次),并且只有在第一次写入之后才能读取变量。由于匿名函数不会再次运行,因此不会再有写入操作发生。

英文:

You pass an anonymous function value to once.Do() (which is Once.Do()), and the return statements are inside that. Which means those return statements want to return from the anonymous function, but it doesn't have any return values:

func Config() (*ConfigMap, error) {
    once.Do(func() {
        // You can't return any values here, only this works:
        return
    })

    // And you do need to return something here:
    return &ConfigMap{}, nil
}

What you may do is create global variables matching the return values of Config(), and the anonymous function should store the values in them. And in Config() you may return the values of these global variables.

var cm *ConfigMap
var cmErr error

func Config() (*ConfigMap, error) {
    once.Do(func() {
        // load config, and store, e.g.
        cm, cmErr = &ConfigMap{}, nil
    })

    return cm, cmErr
}

Do we really need global variables? Since the values returned by Config() are produced by the anonymous function passed to once.Do() which is guaranteed to run only once, yes, you need to store them somewhere to be able to return them multiple times, even when the anonymous function is not called / run anymore (on subsequent calls to Config()).

Question: May there be a data race here?

If Config() is called from multiple goroutines, at least one will write the global variables cm and cmErr, and all will read them. So it's rightful to ask this question.

But the answer is no, the above code is safe. The global variables cm and cmErr are only written once, and this happens before they could be read. Because once.Do() blocks until the anonymous function returns. If Config() (and thus once.Do()) is called simultaneously from multiple goroutines, all will block until the anonymous function completes (once only), and reading the variables can happen only after the first write. And since the anonymous function will not run anymore, no more writes will happen.

答案2

得分: 1

你在once.Do的嵌套func()中调用了return nil, err等语句。相反,你没有从实际函数中返回。

相反,你可以按照以下方式组织你的代码:

func newConfig() (*Config, error) {
    configFilePath, err := filepath.Abs("../build/app.conf")
    if err != nil {
        return nil, err
    }

    // 加载 app.conf
    cfg, err := ini.Load(configFilePath)
    if err != nil {
        return nil, err
    }

    // 获取 app 模式
    mode, err := AppMode()
    if err != nil {
        return nil, err
    }

    c := &ConfigMap{}
    err = cfg.Section(mode).MapTo(c)
    if err != nil {
        return nil, err
    }

    return c, err
}

// 缓存的配置和任何错误。
var (
   cachedConfig *Config
   cachedConfigErr error
)

func Config() (*Config, error) {
  once.Do(func() {
    cachedConfig, cachedConfigErr = newConfig()
  })
  return cachedConfig, cachedConfigErr
}
英文:

You're calling return nil, err and similar from the nested func() inside your once.Do. Conversely, you're not returning from the actual function.

Instead, you can structure your code like this:

func newConfig() (*Config, error) {
    configFilePath, err := filepath.Abs("../build/app.conf")
    if err != nil {
        return nil, err
    }

    // Load app.conf
    cfg, err := ini.Load(configFilePath)
    if err != nil {
        return nil, err
    }

    // Get app mode
    mode, err := AppMode()
    if err != nil {
        return nil, err
    }

    c := &ConfigMap{}
    err = cfg.Section(mode).MapTo(c)
    if err != nil {
        return nil, err
    }

    return c, err
}

// Cached config and any error.
var (
   cachedConfig *Config
   cachedConfigErr error
)

func Config() (*Config, error) {
  once.Do(func() {
    cachedConfig, cachedConfigErr = newConfig()
  })
  return cachedConfig, cachedConfigErr
}

huangapple
  • 本文由 发表于 2017年2月6日 22:00:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/42069615.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定