How Does This Work?

297 views
Skip to first unread message

jlfo...@berkeley.edu

unread,
Mar 6, 2022, 2:46:18 PM3/6/22
to golang-nuts
(go version go1.17.7 linux/amd64)

Consider the following trivial program:
------
package main

import (
        "fmt"
        "os"
)

func main() {
        file := "."
        fileinfo, _ := os.Stat(file)
        fmt.Printf("type of fileinfo = %T\n", fileinfo)
}
------

This runs and produces the output

type of fileinfo = *os.fileStat

Fine, but notice that "fileStat" isn't capitalized. This means this symbol isn't
exported outside the "os" package. Yet, somehow the "fileinfo" variable is assigned
this type.

Indeed, if I try to explicitly use the "os.fileStat" type in the program, the program fails to compile, e.g.

------------
package main

import (
        "fmt"
        "os"
)

func main() {
        var fileinfo *os.fileStat

        file := "."
        fileinfo, _ = os.Stat(file)
        fmt.Printf("type of fileinfo = %T\n", fileinfo)
}
-----
results in

./x3.go:9:16: cannot refer to unexported name os.fileStat
./x3.go:12:14: cannot assign fs.FileInfo to fileinfo (type *os.fileStat) in multiple assignment: need type assertion
./x3.go:12:14: cannot use fs.FileInfo value as type *os.fileStat in assignment: need type assertion

Notice the first error message.

I also don't understand why the other two error message are produced when all I did was to explicitly declare a variable that was previously assigned a value in a short declaration.

What am I missing?

Cordially,
Jon Forrest



Kurtis Rader

unread,
Mar 6, 2022, 3:23:41 PM3/6/22
to jlfo...@berkeley.edu, golang-nuts
It's because os.FileInfo is an interface, not a concrete type. Specifically, it's an alias for io/fs.FileInfo. The concrete type returned by os.Stat() is os.fileStat; a struct that implements the os.FileInfo interface. While you can't refer to a private type like os.fileStat there is nothing prohibiting the os package from returning a private type that satisfies an interface. See, for example, https://github.com/golang/go/blob/45f45444b307cea7c8330b100b30382e642e010f/src/os/stat_unix.go#L15-L26

--
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...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/aef48e1e-e0c7-44e4-b62b-2fd1f6e5157bn%40googlegroups.com.


--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

jlfo...@berkeley.edu

unread,
Mar 6, 2022, 3:32:11 PM3/6/22
to golang-nuts
Thanks for the quick reply. Let's say I wanted to pass "fileinfo" from my program to another function. What would I say in function's argument list for fileinfo's type? From your reply I'm guessing I would give "fileinfo *io/fs.fileinfo" but I'm not sure.

Jon

Brian Candler

unread,
Mar 6, 2022, 3:32:50 PM3/6/22
to golang-nuts
https://go.dev/play/p/pz02pSaVCyl

A function can return a value of a private type (or a pointer to such a type, as you found).  It's a way to return an opaque value that implements an interface.

The receiver in a different package can assign that value, but can't declare additional variables of that type. They can of course call public methods on values of that type - or assign to a variable of a compatible interface type.

Kurtis Rader

unread,
Mar 6, 2022, 3:40:17 PM3/6/22
to jlfo...@berkeley.edu, golang-nuts
On Sun, Mar 6, 2022 at 12:33 PM jlfo...@berkeley.edu <jlfo...@berkeley.edu> wrote:
Thanks for the quick reply. Let's say I wanted to pass "fileinfo" from my program to another function. What would I say in function's argument list for fileinfo's type? From your reply I'm guessing I would give "fileinfo *io/fs.fileinfo" but I'm not sure.

If you're defining the function you would use "os.FileInfo" as the argument type (or a pointer to that type if that's more appropriate).

Martin Schnabel

unread,
Mar 7, 2022, 12:40:43 AM3/7/22
to golan...@googlegroups.com
one important detail i have not found in the other answers is
that stat is declared to return a fs.FileInfo, but printf takes
an empty interface argument and therefor unwraps the interface
value. so if you want to print the actual variable type of interface
variables you can pass a pointer (same is often used with reflection):

// prints "type of &fileinfo = *fs.FileInfo"
fmt.Printf("type of &fileinfo = %T\n", &fileinfo)
> --
> 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...@googlegroups.com
> <mailto:golang-nuts...@googlegroups.com>.
> <https://groups.google.com/d/msgid/golang-nuts/aef48e1e-e0c7-44e4-b62b-2fd1f6e5157bn%40googlegroups.com?utm_medium=email&utm_source=footer>.

Rob Pike

unread,
Mar 7, 2022, 1:07:34 AM3/7/22
to Martin Schnabel, golan...@googlegroups.com
Be careful. You are correct but newcomers to Go often get confused here.

Printf unpacks the interface it is passed to find the concrete element
inside. Doing what reflect requires you to do to discover the type of
an interface is unrelated to the problem at hand.

Watch:

% cat x.go
package main

import "fmt"
import "os"

func main() {
f, _ := os.Stat("x.go")
fmt.Printf("%T\n", f)
}
% go run x.go
*os.fileStat
%

No & necessary. Even though Stat returns an interface, fmt.Printf will
tell you the type inside.

The real thing going on here is that the reflect library allows you to
_read_ unexported values, but not _write_ them. If Printf couldn't
print unexported values (nobody else could either), then debugging
would be a lot harder.

-rob
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/b9a88472-5c3b-fb0e-1abc-1cacf763edf1%40mb0.org.

Brian Candler

unread,
Mar 7, 2022, 3:24:06 AM3/7/22
to golang-nuts
On Sunday, 6 March 2022 at 20:40:17 UTC Kurtis Rader wrote:
On Sun, Mar 6, 2022 at 12:33 PM jlfo...@berkeley.edu <jlfo...@berkeley.edu> wrote:
Thanks for the quick reply. Let's say I wanted to pass "fileinfo" from my program to another function. What would I say in function's argument list for fileinfo's type? From your reply I'm guessing I would give "fileinfo *io/fs.fileinfo" but I'm not sure.

If you're defining the function you would use "os.FileInfo" as the argument type (or a pointer to that type if that's more appropriate).


If you use a pointer to interface, bear in mind that a *os.FileInfo (pointer to interface) is not the same as a *os.fileStat.

*os.fileStat is a pointer to a concrete type. If you assign that to an interface variable, it's boxed in a <type,pointer> wrapper. If you then take a pointer to that, you have a pointer to the <type,pointer> wrapper.

Because an interface value already contains a pointer, there are fewer cases where it makes sense to pass a pointer to an interface value. The common reasons for passing a pointer - avoiding copying large data structures, and being able to pass 'nil' - are already met by a plain interface value.

The only reason I can think of for passing a pointer to an interface is if you need the receiver to be able to mutate the value at the place where it was originally stored.

Ian Lance Taylor

unread,
Mar 7, 2022, 4:20:44 PM3/7/22
to jlfo...@berkeley.edu, golang-nuts
On Sun, Mar 6, 2022 at 11:47 AM jlfo...@berkeley.edu
<jlfo...@berkeley.edu> wrote:
>
> Fine, but notice that "fileStat" isn't capitalized. This means this symbol isn't
> exported outside the "os" package. Yet, somehow the "fileinfo" variable is assigned
> this type.

This has been answered, but I'm going to give a different slant on the
answer. The fact that fileStat is not capitalized means only that no
other package can write os.fileStat. It does not have any effect on
anything other than naming. In particular it does not mean that some
other package can't manipulate a value of type os.fileStat.

Ian
Reply all
Reply to author
Forward
0 new messages