英文:
Does WithContext method need to panic if context is nil?
问题
我想为一个结构体编写一个WithContext
方法,并从net/http
的Request.WithContext
方法中获取灵感。
我的问题是:为什么Request.WithContext
方法在上下文为nil时会引发panic:
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
...
}
我应该也这样做吗?
关于我为什么想创建一个WithContext
方法的更多背景信息:我正在实现一个接口,该接口在其签名中没有提供上下文参数,但我认为实现需要它。
更具体地说,我正在使用Go的官方Redis客户端为gorilla/session
编写一个Redis后端,其中Get
和Set
方法接受context.Context
。
我的想法是,当需要时,使用新的上下文对象对我的Redis存储进行浅拷贝,然后使用它:
type redisStore struct {
codecs []securecookie.Codec
backend Backend // Redis客户端的自定义接口
options *sessions.Options
ctx context.Context
}
func (s *redisStore) WithContext(ctx context.Context) *redisStore {
if ctx == nil {
panic("nil context")
}
s2 := new(redisStore)
*s2 = *s
s2.ctx = ctx
return s2
}
// Backend
type Backend interface {
Set(context.Context, string, interface{}) error
Get(context.Context, string) (string, error)
Del(context.Context, string) error
}
英文:
I want to write a WithContext
method for a struct and am taking inspiration from net/http
's Request.WithContext
.
My question is: why does Request.WithContext
panic if the context is nil:
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
...
}
And should mine as well?
For more context on why I want to create a WithContext
method: I am implementing an interface that does not provide a context parameter in its signature but believe the implementation requires it.
More specifically, I am writing a Redis backend for gorilla/session
using the official Redis client for Go, where the Get
and Set
methods take context.Context
.
The idea is that my redis store will be shallow copied with the new context object, when needed, and then used:
type redisStore struct {
codecs []securecookie.Codec
backend Backend // custom interface for Redis client
options *sessions.Options
ctx context.Context
}
func (s *redisStore) WithContext(ctx context.Context) *redisStore {
if ctx == nil {
panic("nil context")
}
s2 := new(redisStore)
*s2 = *s
s2.ctx = ctx
return s2
}
// Backend
type Backend interface {
Set(context.Context, string, interface{}) error
Get(context.Context, string) (string, error)
Del(context.Context, string) error
}
答案1
得分: 1
恐慌的目的是为了快速失败并拒绝一个nil
上下文,而不改变函数签名。
如果函数不恐慌,那么它必须返回错误以拒绝错误的输入:
func (r *Request) WithContext(ctx context.Context) (*Request, error) {
if ctx == nil {
return nil, errors.New("nil ctx")
}
...
}
然后调用这个函数的人必须处理错误,以避免使用无效的请求:
request, err = request.WithContext(nil)
if err != nil {
}
通过处理错误,你引入了一个控制流分支,失去了方法链式调用的能力。你也不能立即将WithContext
的返回值用作函数参数:
// 不能这样做,因为WithContext也返回一个错误
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)
此外,这将创建一个最终会被垃圾回收的错误实例。所有这些都很繁琐,可用性差,只是为了说“不要给我一个空的上下文”。
关于使用上下文创建一个Redis存储,上下文文档已经很清楚:
> 包context定义了Context类型,它在API边界和进程之间传递截止时间、取消信号和其他请求范围的值。
重要的细节是请求范围。因此,在Redis客户端本身设置上下文与此建议相违背。你应该在每个get/set调用中传递上下文值。
英文:
The purpose of panicking is to "fail fast" and reject a nil
context without changing the function signature.
If the function does not panic then it must return error in order to reject a bad input:
func (r *Request) WithContext(ctx context.Context) (*Request, error) {
if ctx == nil {
return nil, errors.New("nil ctx")
}
...
}
And then who calls this function must handle the error to avoid using an invalid request:
request, err = request.WithContext(nil)
if err != nil {
}
By handling the error you are introducing a control flow branch, and you lose method chaining. You also cannot immediately use WithContext
return value into a function parameter:
// cannot do, because WithContext returns an error too
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)
Also it would create an error instance that will be eventually garbage collected. This all is cumbersome, poor usability and unnecessary alloc simply for saying "don't give me a nil context".
About creating a redis store with a context, the context documentation is clear:
> Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
The important detail is request-scoped. So setting a context in the redis client itself is contrary to this recommendation. You should pass context values at each get/set call.
答案2
得分: 0
如果客户端关闭连接,HTTP请求的上下文将被取消。当上下文被取消时,所有子上下文也将被取消,因此如果传递了nil上下文给WithContext
函数,会导致panic错误。
你是否应该在redis存储中引发panic错误取决于你如何使用该上下文。通常情况下,将上下文包含在结构体中并不是一个好主意。一个可接受的做法是,如果结构体本身就是一个上下文。上下文应该为每个调用创建,应该在该调用的持续时间内存在,然后被丢弃。
英文:
The context of an HTTP request is canceled if the client closes the connection. When the context is canceled, all its child contexts are also canceled, so a nil context would panic then. Because of this, you cannot pass a nil context to WithContext
.
Whether or not your redis store should panic depends on how you are going to use that context. It is usually not a good idea to include a context in a struct. One acceptable way of doing that is if the struct itself is a context. Contexts should be created for each call, should live for the duration of that call, and then thrown away.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论