golang/go

cmd/compile: instantiation cycle in closure function created by method of generic type

zhuah opened this issue · 5 comments

zhuah commented

What version of Go are you using (go version)?

$ go version
go version devel go1.18-9d0ca262bb Wed Dec 15 00:33:55 2021 +0000 darwin/amd6

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env

What did you do?

https://go.dev/play/p/l9gzFUOBT5W?v=gotip

package main

import (
	"fmt"
	"strconv"
	"strings"
)

type StringParser[T any] func(s string) (T, error)

func ParseList[T any](p StringParser[T], s string) ([]T, error) {
	if s == "" {
		return nil, nil
	}
	parts := strings.Split(s, ",")
	vals := make([]T, len(parts))
	for i, part := range parts {
		v, err := p(part)
		if err != nil {
			return nil, err
		}
		vals[i] = v
	}
	return vals, nil
}

func (p StringParser[T]) ToListParser() StringParser[[]T] {
	return func(s string) ([]T, error) {
		return ParseList(p, s)
	}
}

func ToListParser[T any](p StringParser[T]) StringParser[[]T] {
	return func(s string) ([]T, error) {
		return ParseList(p, s)
	}
}

func main() {
	p := StringParser[int](strconv.Atoi).ToListParser()
	fmt.Println(p("1,2,3"))
}

ToListParser is fine to compile,, but the compiler reports errors on StringParser.ToListParser, :

./prog.go:9:19: instantiation cycle:
	prog.go:27:54: T instantiated as []T

as i can see, []T is depends on T, rather than StringParser[T], there shouldn't be a cycle.

I don't know whether this is a bug, or go doesn't allow this usage ?

What did you expect to see?

What did you see instead?

I think the error is correct. StringParser[T]'s ToListParser method has a return type of StringParser[[]T]. This means if we want to instantiate StringParser[int], then we'll also have to instantiate StringParser[[]int] too; which then means we'll also have to instantiate StringParser[[][]int], etc etc.

zhuah commented

@mdempsky thank you, i got it.

it' may better be reported as infinite instantiation instead of cycle ?

which then means we'll also have to instantiate StringParser[[][]int],

Could you explain how this follows from the previous part? There are no method calls for StringParser[[]int], so it looks like there should never be any need to consider StringParser[[][]int].

Similar code in (say) Rust compiles just fine:

struct StringParser<T> {
    f: Box<dyn Fn(String) -> Option<T>>
}

impl<T> StringParser<T> {
    fn to_list_parser(self) -> StringParser<Vec<T>> {
        todo!()
    }
}

fn parse_list<T>(p: StringParser<T>, s: String) -> Option<Vec<T>> {
    todo!()
}

fn main() {
    let p = StringParser { f: Box::new(|s| str::parse::<i64>(&s).ok()) };
    (p.to_list_parser().f)("1,2,3".to_string());
}

Could you explain how this follows from the previous part?

Go allows method calls to happen dynamically through interface assertions or reflection. As a result, if a type is instantiated, all of its methods must be instantiated too, even if not statically called.

There are certainly cases where we could determine this isn't actually needed, but that would complicate the language specification.