Hi all,
Many moons ago I wrote a proposal
<https://groups.google.com/g/golang-nuts/c/o3fFHNhiNQs/m/bAyb_5dOCAAJ>
to make /execution context/ a fundamental concept in Go, effectively
moving `context.Context` to being part of the language, not just an
extension.
The main reason to do this is that then it would not be up to the source
maintainer to have to do the refactoring it takes to retrofit `ctx
context.Context` arguments passed to functions as the first position, as
has become the common boilerplate. In some places, this is impossible:
for instance, the io.Reader and related interfaces forbid a
`context.Context` argument, so a reader cannot know where it is being
read from.
Why does that matter, you might ask? The main reason is for
observability: tracing, logging, debugging, etc. Of course, context
should in general not be used to alter behavior, but for observability,
it seems like it would be fair game for the append–only map that
`Context` provides, to be available even when working with less than
stellar code.
I'm wondering how people would feel about this now?
A summary of my original proposal would be to add a "soft" keyword
("context") which is only meaningful when used before "var", but would
not be prohibited from being a variable name. I did some
experimentation with the go parser and found I could get code with
'context' added to is passing the parsing tests, so I'm pretty sure this
would work without slowing things down. That's the syntax I'll use in
this message, but of course this is subject to feedback and taste.
Some basic principles stated as text for discussion; there are code
examples in the original proposal from August 2017.
* Semantically, creating a new scope creates a new execution context.
Hopefully that is a tautology.
* Context Variables can be declared in any execution context, using
`context var` instead of plain `var`, and assuming the `context var`
is in scope of the observer, to all execution scopes created from
that execution context, including goroutines.
* The reverse is not true: the only way you can see changes to a
variable declared in a high level context, is if the variable has a
pointer, and the sub–level context changes it (i.e., there's no
"magic const effect" of declaring a variable with context var.)
* Functions that don't declare context variables use the same context
as their caller for retrieving context variables.
o Declaring it without assigning it in a function allows you to
read it, without changing the context. Like other variables
declared without an assignment, reading it from a context with
no parent context that has assigned it would reveal the zero value.
o You can refer to the old value in the rvalue of the declaration,
or at any point from when you declare it to when you assign it
within a function. However, having an assignment in the scope
should be thought of creating a new context immediately with an
implicit assignment at the declaration. I think this behavior
keeps the semantics as unambiguous as possible, and avoids
having to do funny stuff when assigning the variable late in a
function.
* Scope would work differently. While the /state/ of the value
follows the execution context, the visibility of the variable would
be defined by (a) the package the variable is declared in, and (b)
the Case of the variable.
o `context var localInfo` can only be read and written in the
package it is declared in.
o While reading the rest of this, be careful not to think of the
Monty Python Spam song, as this is about Spans, which are not
the same.
o `context var OpenSpan Span` declares an exported context
variable, which can be accessed as below:
o declaring `context var tracing.OpenSpan` would mean to access
the `OpenSpan` context variable exported by the `tracing`
package. This would only be a compile error if the `tracing`
package has no `context var OpenSpan x` statement in it.
+ Simply /reading /the context variable from outside the
package might not need a special syntax:
`(tracing.OpenSpan)` to read it might work just fine, to
allow eg:
`tracing.OpenSpan.Log("happening", tracing.Tag("id", id))`
o Finally, `context var tracing.OpenSpan =
tracing.OpenSpan.New("canBeSlowFunc", tracing.InternalKind,
...)` is an example of how you could work with the exported
context variables from a package. The example there is an
idiomatic way for a function to declare that it is doing
something that can be expensive, and so is worthy of tracing
representation with a new Span. `Span.New()` would be a
function that returned a new sub–span of the original Span.
Anyway I think that's the nutshell of it. Thoughts/questions/concerns?
Cheers,
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.
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/24843040-a0f2-4054-8912-acf9defb7697%40vilain.net.