Best practices for file scoping

5,648 views
Skip to first unread message

Mickey Killianey

unread,
Nov 17, 2010, 7:00:07 AM11/17/10
to golang-nuts
I have a package with four or five files, and I want to add a variable
with what in other languages would have file-local scope.

Since Go doesn't support file-local scope variables (although init()
seems to be a file local method?)...is there a convention that people
are using to simulate what would normally be done with file-local
scope?

For example, when debugging code, I often add a "logger" object to
some of the files, but that causes a name collision when two or more
files in a package have a logger. I'm working around this by
simulating a namespace:

In foo.go: var foo_logger = mylogger.GetLogger("foo")
In bar.go: var bar_logger = mylogger.GetLogger("bar")

Is there a "best practice" pattern for what to do when you find
yourself wanting to use a file-local variable like this?


chris dollin

unread,
Nov 17, 2010, 7:07:34 AM11/17/10
to Mickey Killianey, golang-nuts

Use smaller packages?

Chris

--
Chris "allusive" Dollin

Mickey Killianey

unread,
Nov 17, 2010, 3:21:50 PM11/17/10
to golang-nuts
Yup, I was half-expecting that answer...:-)...although that would
introduce other issues for me.

My package is on the larger side because some of the parts communicate
in ways that I don't want to be publicly visible. Is it correct that
putting them in different packages forces me to make those
implementation details globally public? (Or is there something
brewing that falls between package-private and global?)

And since the types need to have *mutual* knowledge of each other,
breaking them into two separate packages wouldn't even compile unless
I factored out a third API package that publicly defined how they
privately communications. Meh...I don't think I'm willing to make
that tradeoff just for the sake of having "var _logger" in both files.

As an attempt to offer constructive feedback, has anyone suggested a
Python-esque syntax to enable file-scoped variables?

For example, what if Go adopted the convention that variable names
starting with double-underscore were textually munged during parsing
to be file-private, so that references to...
var __logger
...in foo.go were internally parsed to be...
var __foo_go_hash5469ac52_logger
...since they aren't visible post-compilation, anyway?

Mick.


On Nov 17, 12:07 pm, chris dollin <ehog.he...@googlemail.com> wrote:

chris dollin

unread,
Nov 17, 2010, 3:43:43 PM11/17/10
to Mickey Killianey, golang-nuts
On 17 November 2010 20:21, Mickey Killianey <mickey.k...@gmail.com> wrote:
> Yup, I was half-expecting that answer...:-)...although that would
> introduce other issues for me.

I didn't think it would be an immediate solution, although
it's where I'd start.

(Aside: these are my thoughts as a Go user; I'm just some
guy, not someone Speaking For Go. Salt to taste.)

> My package is on the larger side because some of the parts communicate
> in ways that I don't want to be publicly visible.   Is it correct that
> putting them in different packages forces me to make those
> implementation details globally public?  (Or is there something
> brewing that falls between package-private and global?)

No (and I think that's a good thing).

I think a package is a single entity that is divided into
files purely for convenience rather than semantics -- for
example less-strongly-related entities can be in different
files, so that the reader finds like things together without
clutter, or so that there's more visibility of which bits change
in change control.

Because the package is a single entity, with the files
mere convenience, it's OK for developers working on one
file to know about all the others. So for the loggers example
we started with, any old naming scheme will do, and if
we want a convention -- and to my mind that /is/ an IF -- then
we could, say, use the file's leafname as a prefix (with due
consideration for characters allowed in filenames but not
Go identifiers).

> And since the types need to have *mutual* knowledge of each other,
> breaking them into two separate packages wouldn't even compile unless
> I factored out a third API package that publicly defined how they
> privately communications.  Meh...I don't think I'm willing to make
> that tradeoff just for the sake of having "var _logger" in both files.

Nor I -- but I wouldn't fear the creation of an API package if
that was the price of breaking down a too-big package into
more coherent smaller ones. It All Depends on how big and
how coherent the packages in question are.

> As an attempt to offer constructive feedback, has anyone suggested a
> Python-esque syntax to enable file-scoped variables?
>
> For example, what if Go adopted the convention that variable names
> starting with double-underscore were textually munged during parsing
> to be file-private, so that references to...
>    var __logger
> ...in foo.go were internally parsed to be...
>    var __foo_go_hash5469ac52_logger
> ...since they aren't visible post-compilation, anyway?

My feeling is that the problem isn't big enough to need a
linguistic solution, but I haven't written enough big
packages to feel that pain even if it exists ...

yy

unread,
Nov 17, 2010, 5:28:38 PM11/17/10
to Mickey Killianey, golang-nuts
2010/11/17 Mickey Killianey <mickey.k...@gmail.com>:

> My package is on the larger side because some of the parts communicate
> in ways that I don't want to be publicly visible.   Is it correct that
> putting them in different packages forces me to make those
> implementation details globally public?  (Or is there something
> brewing that falls between package-private and global?)

You can use a package for the common stuff you don't want to export.
Let's say you have a.go and b.go in your package, and a common.go file
with unexported bits, you could make a package "common" and import it
from the packages "a" and "b". When later you use one of these
packages you won't have access to anything inside "common" if you
don't want it.

--
- yiyus || JGL . 4l77.com

Russ Cox

unread,
Nov 18, 2010, 11:56:38 AM11/18/10
to golan...@googlegroups.com
We spent a long time on this and decided that in Go
file boundaries are never relevant, nor is the order of
top-level declarations.  This makes it nearly trivial to
move code between files as makes sense for organization.
(The one exception is imports, but making imports work
"at a distance" seemed even worse.)

I have found myself in the situation you are in on
occasion, and I always end up doing one of these:

1. Use a per-package logger variable.
   If this is just for debugging, that's often fine.

2. Identify what the semantic split of the logging
   is, and use that in the name.  For example, if
   your package is readers and writers and that's
   how the logging breaks down, then readLogger
   and writeLogger seem reasonable.

Russ

Andrew Gerrand

unread,
Nov 18, 2010, 6:02:33 PM11/18/10
to Mickey Killianey, golang-nuts
On 17 November 2010 23:00, Mickey Killianey <mickey.k...@gmail.com> wrote:
> Since Go doesn't support file-local scope variables (although init()
> seems to be a file local method?)

In addition to what Russ has said, I just want to point out that you
can have multiple init functions in a single file. This is because
multiple init functions are permitted in a single package, and file
boundaries are unimportant.

Andrew

Mateusz Czapliński

unread,
Nov 19, 2010, 3:30:09 AM11/19/10
to golang-nuts

On Nov 19, 12:02 am, Andrew Gerrand <a...@golang.org> wrote:
> On 17 November 2010 23:00, Mickey Killianey <mickey.killia...@gmail.com> wrote:
>
> > Since Go doesn't support file-local scope variables (although init()
> > seems to be a file local method?)
>
> In addition to what Russ has said, I just want to point out that you
> can have multiple init functions in a single file. This is because
> multiple init functions are permitted in a single package, and file
> boundaries are unimportant.
>
> Andrew

8-O !!!

and Playground sayeth, "Yes, thou canst."

Mickey Killianey

unread,
Nov 19, 2010, 6:38:32 AM11/19/10
to Andrew Gerrand, golang-nuts

Ooh...that is very handy to know.

I had been listing all my declarations and then trying to remember to init them all.

Are multiple init methods guaranteed to happen in the order they appear in the file?

On 18 Nov 2010 23:03, "Andrew Gerrand" <a...@golang.org> wrote:

On 17 November 2010 23:00, Mickey Killianey <mickey.k...@gmail.com> wrote:

> Since Go doesn't s...

chris dollin

unread,
Nov 19, 2010, 6:44:57 AM11/19/10
to Mickey Killianey, Andrew Gerrand, golang-nuts
On 19 November 2010 11:38, Mickey Killianey <mic...@killianey.com> wrote:
> Ooh...that is very handy to know.
>
> I had been listing all my declarations and then trying to remember to init
> them all.

You can initialise them when you declare them.

> Are multiple init methods guaranteed to happen in the order they appear in
> the file?

No. "they execute in unspecified order" (spec).

But:

Within a package, package-level variables are initialized, and constant
values are determined, in data-dependent order: if the initializer of A
depends on the value of B, A will be set after B. It is an error if such
dependencies form a cycle. Dependency analysis is done lexically:
A depends on B if the value of A contains a mention of B, contains a
value whose initializer mentions B, or mentions a function that mentions B,
recursively. If two items are not interdependent, they will be initialized
in the order they appear in the source.
(spec)

roger peppe

unread,
Nov 19, 2010, 7:03:46 AM11/19/10
to chris dollin, Mickey Killianey, Andrew Gerrand, golang-nuts
On 19 November 2010 11:44, chris dollin <ehog....@googlemail.com> wrote:
> But:
>
> Within a package, package-level variables are initialized, and constant
>  values are determined, in data-dependent order: if the initializer of A
> depends on the value of B, A will be set after B. It is an error if such
>  dependencies form a cycle. Dependency analysis is done lexically:
> A depends on B if the value of A contains a mention of B, contains a
> value whose initializer mentions B, or mentions a function that mentions B,
>  recursively. If two items are not interdependent, they will be initialized
>  in the order they appear in the source.
> (spec)

it's perhaps worth adding that code in an init function does not count
as an initializer.
the following code does prints "6 0" not "6 6":

package main
import "fmt"
var x int
var y = x
func init() { x = 6 }
func main() { fmt.Println(x, y) }

chris dollin

unread,
Nov 19, 2010, 7:10:17 AM11/19/10
to roger peppe, Mickey Killianey, Andrew Gerrand, golang-nuts
On 19 November 2010 12:03, roger peppe <rogp...@gmail.com> wrote:
> On 19 November 2010 11:44, chris dollin <ehog....@googlemail.com> wrote:
>> But:
>>
>> Within a package, package-level variables are initialized, and constant
>>  values are determined, in data-dependent order: if the initializer of A
>> depends on the value of B, A will be set after B. It is an error if such
>>  dependencies form a cycle. Dependency analysis is done lexically:
>> A depends on B if the value of A contains a mention of B, contains a
>> value whose initializer mentions B, or mentions a function that mentions B,
>>  recursively. If two items are not interdependent, they will be initialized
>>  in the order they appear in the source.
>> (spec)
>
> it's perhaps worth adding that code in an init function does not count
> as an initializer.

No "perhaps" about it, I think -- it's an important clarification.

Davis Ford

unread,
May 10, 2015, 12:44:06 AM5/10/15
to golan...@googlegroups.com
I know this is an ancient thread, but I thought I'd see if anyone has any clever tricks for getting around this with unit tests.

I have a package that has a handful of files.  In other languages, file-scope variables are very useful for tests, as I often declare a list of of variables at the top of the test (e.g. the entity under test, as well as several other dependencies it needs that I can inject into it to mock its behavior, etc.)  This is becoming problematic in golang.  Many times the list of these variables across files is similar -- similar dependencies, etc., and I need to new them up for the test, but now they collide across test files.

I'm looking at the following possibilities:

* Move the declaration into every test function so they scoped at the function, but this just repeats code and becomes very verbose. 
* Put them all in a struct and reference them as fields hanging off the global test struct declared in each test file but this is tedious and makes the code less clear (IMO)
* Just rename them to similar but different names in each test file, (this is what the OP was talking about doing)
* Refactor all the code to try to work around this -- untenable

dnj...@gmail.com

unread,
May 25, 2020, 1:52:02 PM5/25/20
to golang-nuts
Me too. Running into the same exact situation.

Davis Ford

unread,
May 25, 2020, 8:43:08 PM5/25/20
to dnj...@gmail.com, golang-nuts
Use Ginkgo -- great test runner and the Gomega test matcher is awesome too.  Works out of the box also with straight up go test.

That was my solution back in 2015, and is still my solution today....still writing golang.

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/s9ShMsCLuXI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/a90b8b93-b9d8-4fc1-a666-63b355ebdef0%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages