How to define type of list?

I would like to define a type of predefined list ["one", "two", "three"]:

type T ????
func MyFunc(t T) {}

So, now when I call to MyFunc it should allow to use ["one"], ["one", "two"], ["three"], etc. but not ["other value"]

MyFunc(["one"]) // Correct
MyFunc(["one","two","three"]) // Correct
MyFunc(["one", "other"]) // Incorrect
MyFunc([""]) // Incorrect

Also, repeating should not be allowed:

MyFunc(["one", "two", "one"]) // Incorrect: "one" repeated twice

Ordering should not matter:

MyFunc(["one", "two"]) // Correct
MyFunc(["two", "one"]) // Correct

So, how can we define a particular list of type? I think I need interface to do so? Not sure how to implement.

My understanding here is: you want to limit strings to certain values. Maybe something along these lines?

// Global variable to hold our valid values
var acceptableStrings = []string{"one", "two", "three"}

func isValid(s string) bool {
	for _, v := range acceptableStrings {
		if v == s {
			return true
		}
	}
	return false
}

func MyFunc(items []string) error {
	for _, v := range items {
		if !isValid(v) {
			return fmt.Errorf("Invalid value: %v", v)
		}
	}
	return nil
}

Then to test it:

func main() {
	testCases := [][]string{
		{"1", "2"},
		{"First", "Second", "Third"},
		{"one", "Second", "three"},
		{"one"},
		{"one", "two", "three"},
	}
	for _, v := range testCases {
		if err := MyFunc(v); err != nil {
			fmt.Println(err)
		} else {
			fmt.Println("Acceptable values:", v)
		}
	}
}

… which produces:

Invalid value: 1
Invalid value: First
Invalid value: Second
Acceptable values: [one]
Acceptable values: [one two three]

Program exited.

Run it yourself here:

Thank you for the answer. No similar things with type or interface? Instead of throwing error manually?

I am looking for behavior like in typescript:

// TypeScript example
interface AnimationOptions {
  easing: "ease-in" | "ease-out" | "ease-in-out";
}

Ah, sorry. This example also doesn’t satisfy my query as in the post as I want to have one of or multiple of. But I am looking for something like that.

Ah - one easy way to make your intent known would be to create a typed string like so:

type MyString string

const (
	MyStringOne   MyString = "one"
	MyStringTwo   MyString = "two"
	MyStringThree MyString = "three"
)

func MyFunc(v MyString) {
	fmt.Println(v)
}

The good: this clearly indicates intent. The bad: you can pass in an invalid value as a literal and the compiler doesn’t care:

// Results in following error:
// cannot use s (variable of type string) as MyString value in argument to MyFunc
s := "bad value"
MyFunc(s)
// Compiles just fine
MyFunc("Bad value literal")

Check this answer out for different approaches:

Also - the safety that TypeScript provides is a compile-time check. But you can easily override that so I don’t think you can truly count on those values only ever being the values in your interface. Consider the following:

interface AnimationOptions {
  easing: "ease-in" | "ease-out" | "ease-in-out";
}

// Works fine
let opts: AnimationOptions = {
    //@ts-ignore
    easing: "bad value"
};

// Works fine
let second = copyOpts({
    easing : 'uh oh'
});

function copyOpts(opts: any) : AnimationOptions {
    return {...opts}
}

My point is: if you wanted true safety in your TypeScript example you’d still have to do a runtime check. Because often things come from async calls (in my experience at least) and interfaces offer the illusion of safety there where there is actually very little.

// Compiles just fine
MyFunc("Bad value literal")

That’s what I don’t want. It should taken care by compiler. So if bad value passed, it should throw an error.

OK and how would you guard against that in your typescript example? How do you guard against my copyOpts example? No matter what, you’re going to have to:

  1. Have some sort of runtime check.
  2. Potentially have some undesirable results when something mutates the value during runtime.

If the latter is what you would do in TypeScript, just use the type MyString string option. If the former, do a runtime check of some sort. You can also use the unexported struct method linked to in the StackOverflow answer above if you wish.

I cannot think of any way to deal with this in Go, but there are workarounds that can get you part of the way there, such as what Dean suggested with the const MyStringOne MyString, or, if you want to make it impossible to use some other string value, you could do this:

type Something interface {
    // any method starting with a lowercase character so that the
    // interface cannot be implemented by anything outside of this
    // package.
    something()
}

type one struct{}
func (one) something() { }
type two struct{}
func (two) something() { }
type three struct{}
func (three) something() { }

var (
    One Something = one{}
    Two Something = two{}
    Three Something = three{}
)

func MyFunc(somethings ...Something) {
    for _, s := range somethings {
        switch s {
        case One:
            // do something
        case Two:
            // do something
        case Three:
            // do something
        default:
            // No way to ensure the full set is matched.
            panic(fmt.Errorf("Unknown Something: %T", s))
        }
    }
}

func main() {
    MyFunc(One, Two)
}

There’s nothing stopping you at compile time from writing:

MyFunc(One, One)

I’m not familiar with linters, but I suspect it is possible to define a linter that can catch this and give you a warning.

Is there a way to catch this in TypeScript? I wonder what happens if you have this:

type Something = "one" | "two" | "three"

function MyFunc(somethings: Something[]) {
    somethings.push("one");
    return myFuncInternal(somethings)
}

function myFuncInternal(somethings: Something[]) {
}

I had that same thought but also am not familiar enough with linters to know if you could write a linting rule for that. What the TSC is doing is more or less equivalent to a linting rule, which is why it’s easy to get around it (like I noted above). I wonder if you could use something like this:

And define a rule that disallows string literals being passed into MyFunc, which would then require a variable to be passed in. And in that case the compiler will complain that you can’t use type string as MyString.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.