Many have written about this; there's an index on Go wiki under ExperienceReports#context <https://github.com/golang/go/wiki/ExperienceReports#context> including my own writing for the 2016 advent calendar.
I would like to propose a simpler way to solve the "optional variable access" part of context as a language feature. The core problem I'm trying to solve is the necessity for large scale refactoring to add the ctx variable to every caller in a stack. I've done this before for the purpose of adding log tagging, tracing, cancellation, and database transactions/savepoints management. I believe it is a good fit for those cases. I've also seen bad usages of it - people shoehorning required variables into it to escape the difficulties of rethinking their design. The basics: - the runtime adds an extra stack argument for its equivalent of the Context structure. - this argument is not directly accessible - it may be eliminated/optimized out by the compiler where not used - it may be "unrolled" to more than one stack argument for performance - a special keyword added for declaring/accessing 'context variables' - assigning to a context variable creates a new context - may make cancelation easy and natural *Setting context variables* So, for example, this use of context.Context: type myPrivateType struct{} var myPrivateKey myPrivateType func someFunc(ctx context.Context, args... interface{}) { localInfo := &LocalInfo{} ctx := context.WithValue(ctx, myPrivateKey, localInfo) ... } Would instead be: func someFunc(args... interface{}) { context var localInfo := &LocalInfo{} ... } *Retrieving context variables* *Retrieving* a value would change from: func otherFunc(ctx context.Context, args... interface{}) { localInfo, ok := ctx.Value(myPrivateKey).(*LocalInfo) ... } To: func otherFunc(args... interface{}) { context var localInfo *LocalInfo ... } *Mid-function variable re-assignment* Re-assigning a previously declared variable would have the same effect as re-assigning a ctx variable in scope: func someFunc(ctx context.Context, args... interface{}) { localInfo, ok := ctx.Value(myPrivateKey).(*LocalInfo) ... if somecondition { ctx = context.WithValue(ctx, myPrivateKey, localInfo) } ... } Would be equivalent to: func someFunc(ctx context.Context, args... interface{}) { context var localInfo *LocalInfo ... if somecondition { localInfo = &LocalInfo{} } ... } I wouldn't treat this exact equivalence as a hard rule; this re-assigning would only be expected to affect that particular context variable. *Public context variables* It could also be possible to access other packages' public context variables: func someFunc(args... interface{}) { context var pkg.FooVariable otherPkg.SomeType ... } This declaration is a little weird, because it includes a package name in the variable name. The immediate question is, is this variable accessed later as "pkg.FooVariable" or just "FooVariable"? I would lean towards the former to be less surprising and to avoid potential namespace clashes. This could be useful for log tagging; APIs would look like; context var logging.Tags []zap.Field logging.Tags = append(logging.Tags, zap.String("rqID", rqUUID)) Where logging is some project-global logging module. This isn't quite the pattern I used in my context logging post, which was: logging module: // WithRqId returns a context which knows its request ID func WithRqId(ctx context.Context, rqId string) context.Context { return context.WithValue(ctx, requestIdKey, requestId) } calling package: func RequestHandler(w http.ResponseWriter, r *http.Request) { rqId := uuid.NewRandom()* rqCtx := logging.WithRqId(httpContext, rqId) * ... This one-line style of tagging a context in that last block could be supported with this sort of call: context var logging.Tags := logging.WithRqID(rqID) In this instance, as logging.WithRqID is evaluated before the logging.Tags context variable is assigned, it accesses the prior value. The function returns the new variable instead of an entire context.Context struct. *Context variable scope* The scope of a context variable would be essentially the same as in context: it passes down, but not up. Declaring a context variable without immediately assigning it will behave as in context: if it's there, you get the prior value. If it's not, you get a zero value (well, context gives you a nil, but the idea is the same). This style makes it easier to identify use of uninitialized context variables: func badFunc(args... interface{}) { context var logging.Logger zap.Logger logging.Logger.Info("in badFunc()", zap.Object("args", args)) ... } It's quite easy to see here that the logging.Logger variable might be being used uninitialized. It also looks awkward. To compare this to the logging pattern I described in my advent calendar post; func betterFunc(ctx context.Context, args... interface{}) { logging.WithContext(ctx).Info("in betterFunc()", zap.Object("args",args)) ... } This pattern should hide this, enabling APIs like this: func goodFunc(args... interface{}) { logging.Info("in betterFunc()", zap.Object("args",args)) ... } As this is less awkward than the 'uninitialized context variable' use case, I would hope that this style would become more natural and popular than the anti-pattern of required context variables. *Context Cancellation* This system could be used to re-implement context cancellation; using the example from the context documentation: func Stream(ctx context.Context, out chan<- Value) error { for { v, err := DoSomething(ctx) if err != nil { return err } select { case <-ctx.Done(): return ctx.Err() case out <- v: } } } This could be written as (assuming the convention is that cancellation happens via a context variable in the sync package): func Stream(out chan<- Value) error { context var sync.Done for { v, err := DoSomething() if err != nil { return err } select { case <-sync.Done: return ctx.Err() case out <- v: } } } *Prior Art & Approaches in other languages* As far as I know, only Perl 6 has this concept as an explicit language feature, also called "context variables" at some point (IIRC), which it now calls "the * twigil <https://docs.perl6.org/language/variables#The_*_Twigil> ". Comments/thoughts welcome! Sam -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.