英文:
Design of Context interface
问题
我的问题是关于Context
接口的设计选择。如果我想从一个parent
创建一个child
上下文,我可以这样做:
child, cancel := context.WithTimeout(parent, timeout)
如果WithTimeout
是接口的一部分,我们可以简单地写成:
child, cancel := parent.WithTimeout(timeout)
这样看起来更加清晰。它更短,而且不需要import context
。
为什么生成子上下文的函数不是Context
接口的一部分呢?
英文:
My question is about a design choice for the Context
interface. If I want to create a child
context from a parent
I can do something like:
child, cancel := context.WithTimeout(parent, timeout)
Wouldn't it be nicer if WithTimeout
was part of the interface, so that we could simply write:
child, cancel := parent.WithTimeout(timeout)
It seems so much cleaner to me. It is shorter, and there is no need to import context
.
Why are the functions to produce child contexts not part of the Context
interface?
答案1
得分: 6
这是context.Context
类型:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
很简单。如果你要编写它的实现,你能做到吗?是的,很容易。因为没有"setter"方法,每个方法只需返回一个默认值或零值,这就是一个"有效"的实现。这正是背景和TODO上下文所做的(context.Background()
和context.TODO()
)。
如果你要在Context
接口本身中添加从现有上下文"派生"新上下文的函数(例如context.WithCancel()
,context.WithDeadline()
等),那就需要为所有函数提供一个(有效的)实现,这是不可行的;而且通常不需要同时使用所有这些函数,因此这将是一种资源浪费。
扩展是负责添加这些实现的。如果你查看context
包的实现:context/context.go
,你会看到不同的context.Context
实现用于不同的"派生"或"扩展":
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
显然,我们可以为context.Context
创造其他有用的扩展,这些扩展不在context
包中。如果你有一个新的想法,你会将它添加到Context
接口中吗?这将破坏所有现有的实现,因为显然你的新想法在其他人当前的实现中没有被实现。
英文:
This is the context.Context
type:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
It's simple. If you were to write an implementation of it, could you do it? Yes, easily. As there are no "setter" methods, each method can just return a default / zero value, and it's a "valid" implementation. That's exactly what the background and TODO contexts do (context.Background()
and context.TODO()
).
If you would add the functions that derive new context from an existing one (e.g. context.WithCancel()
, context.WithDeadline()
etc.) as part of the Context
interface itself, that would require to provide a (valid) implementation for all, which is not feasible; and also all of them at the same time is rarely needed, so it would be a waste of resources.
Extensions are responsible to add the implementations. If you look at how the context
package is implemented: context/context.go
, you'll see different context.Context
implementations for the different "derivatives" or "extensions":
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
Obviously we can make up other useful extensions for context.Context
which are not in the context
package. If you have a new idea, would you also add that to the Context
interface? That would break all existing implementations, as obviously your new idea is not implemented in others' current implementations.
答案2
得分: 1
我认为有两个原因:
-
这是因为
WithContext
与parent
没有任何关系 - 例如,父级不应该需要也不应该知道你可以从它创建一个子上下文的事实。在Go的理念中,接口应该尽可能简化。 -
这样更易读和清晰地了解输出结果。在当前的实现中,
someVar, _ := context.WithTimeout(value)
被读作some variable is a new (:=) context with a timeout
。在你提出的版本中,它是someVar, _ := parent.WithTimeout(value)
,这有点更加隐晦。
英文:
IMHO there are 2 reasons:
-
It is because
WithContext
has nothing to do with theparent
- e.g. parent should not need and should not have any ideas about the fact you can create a child context from it. In Go ideology interface should be as minimal as possible. -
It is more readable and clear what are you getting as output. In the current implementation
someVar, _ := context.WithTimeout(value)
is readed assome variable is a new (:=) context with a timeout
. In your suggested version it'ssomeVar, _ := parent.WithTimeout(value)
it's a bit more obscure.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论