Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text/template: Methods vs funcs discrepancy #20503

Closed
bep opened this issue May 26, 2017 · 4 comments
Closed

text/template: Methods vs funcs discrepancy #20503

bep opened this issue May 26, 2017 · 4 comments

Comments

@bep
Copy link
Contributor

@bep bep commented May 26, 2017

▶ go version
go version go1.8.1 darwin/amd64
▶ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/bep/go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/n6/s_85mm8d31j6yctssnmn_g1r0000gn/T/go-build150403565=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"

The following Go program:

package main

import (
	"bytes"
	"fmt"
	"text/template"
)

type A struct {
	Func func() []string
}

func (A) Method() []string {
	return []string{"c", "d"}
}

func main() {
	a := A{}
	a.Func = a.Method

	for _, v := range a.Method() {
		fmt.Println(">>", v)
	}

	for _, v := range a.Func() {
		fmt.Println(">>", v)
	}

	for _, tpl := range []string{"{{ range .Method  }}{{ . }}{{ end }}", "{{ range .Func  }}{{ . }}{{ end }}"} {

		var buf bytes.Buffer
		tmpl, err := template.New("").Parse(tpl)
		if err != nil {
			panic(err)
		}
		if err := tmpl.Execute(&buf, a); err != nil {
			panic(err)
		}

		fmt.Println(buf.String())
	}

}

Prints

>> c
>> d
>> c
>> d
cd
panic: template: :1:9: executing "" at <.Func>: range can't iterate over 0x10e3320

This is, to me, unexpected behaviour. The method is invoked before it is ranged on, the func not.

@odeke-em
Copy link
Member

@odeke-em odeke-em commented May 27, 2017

@mvdan
Copy link
Member

@mvdan mvdan commented Sep 11, 2017

I'm not experienced with the text/template package, but since this issue has had little attention, I've played a bit with it.

@bep, I think this is working as intended - even though from a user's point of view it's a bit unintuitive.

Reading the package godoc:

.Field
The result is the value of the field.
.Method
The result is the value of invoking the method with dot as the receiver, dot.Method().

Note that, in your example, both a.Method and a.Func have type func() []string.

So, following the package's own godoc, it makes sense that {{ range .Method }} would equate range []string{...}, while {{ range .Func }} would equate range func() []string { return ... }.

You can also fix this by using call, i.e. {{ range call .Func }}.

So I would say this is not a bug in the sense that the package is not broken. You could argue that it could be made more user-friendly, but it could also lead to more complex rules. And it would probably warrant a proposal, not a bug report.

@robpike
Copy link
Contributor

@robpike robpike commented Sep 11, 2017

It is a little confusing but yes it is working as intended. Use 'call'.

@robpike robpike closed this Sep 11, 2017
@bep
Copy link
Contributor Author

@bep bep commented May 11, 2018

Just in case other people are slow thinking as myself. I just figured out a workaround that is so obvious that I'm a little bit embarrassed showing it:

My use case was/is:

I had a map sent to the templates with some data that was rarely used, so I wanted to lazily load it without messing with the API (i.e. the map key/value range etc.).

The workaround is, of course, to create a map type with methods:

package main

import (
	"bytes"
	"fmt"
	"text/template"
)

type MyMap map[string]interface{}

func (m MyMap) MyFunc() string {
	f := m["func"].(func() string)
	return f()
}

func main() {

	m := make(MyMap)

	m["func"] = func() string {
		return "Hello"
	}

	tpl := "{{ .MyFunc }}"

	var buf bytes.Buffer
	tmpl, err := template.New("").Parse(tpl)
	if err != nil {
		panic(err)
	}
	if err := tmpl.Execute(&buf, m); err != nil {
		panic(err)
	}

	fmt.Println(buf.String())

}

The above example is a little bit constructed, but it shows the point, I hope.

@golang golang locked and limited conversation to collaborators May 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.