1

The following example defines a series of port numbers starting at 3333 using iota.

package main

import (
    "fmt"
)
const (
FirstPort = iota+3333
SecondPort
ThirdPort
)
func main() {
    hostAndPort := "localhost:"+fmt.Sprint(SecondPort)
    fmt.Printf("%s", hostAndPort ) 
    // Output:
    // localhost:3334
}

When combining hostname and ports, I'd like to avoid having to wrap the port constant in fmt.Sprint and simply write, for example, "localhost:"+SecondPort. Is there a way to use iota to define the port numbers as string constants, e.g "3334"?

The following doesn't work:

FirstPort = string(iota + 3333)

Neither does

FirstPort = fmt.Sprintf("%d", iota + 3333)
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Mike Ellis
  • 1,120
  • 1
  • 12
  • 27
  • 1
    I don't see the value in using iota to define ports if you want them to be strings and there are only three of them. But what you can do, as it is now, is use `hostAndPort := fmt.Sprintf("localhost:%d", FirstPort)` – martskins Sep 06 '18 at 16:24
  • 1
    You cannot define a string constant using `iota`, no. – Adrian Sep 06 '18 at 16:25
  • @lascort My example is just that, an example. The real app uses a block of dozens of ports. – Mike Ellis Sep 06 '18 at 16:26
  • @MikeEllis in that case I'd probably go with specifying a config file for the user to specify the ports they want to use. Taking control of a dozen ports, with no option to change them, seems like a bad idea. But to stick to the question.. use `fmt.Sprintf` – martskins Sep 06 '18 at 16:31
  • 1
    @lascort It's a closed system industrial application where the configuration is fixed. I don't have to time to explain it all here but a fixed block of ports makes sense in this context. If Adrian's comment is correct, then I suppose I'll end up using go:generate to create the string constants. – Mike Ellis Sep 06 '18 at 16:43

2 Answers2

6

Quoting from Spec: Iota:

Within a constant declaration, the predeclared identifier iota represents successive untyped integer constants.

So iota provides you integer constants. If we want string constants, we need to find a way to convert an integer to its base-10 string representation. This way must be a constant expression, else we can't use it in a constant declaration.

Unfortunately for us, a simple type conversion from integer to string will not yield the base-10 representation of the numerical value, but:

Converting a signed or unsigned integer value to a string type yields a string containing the UTF-8 representation of the integer.

So the result will be a string holding a single rune, whose value (the Unicode codepoint) is the source number.

Also calling "converter" functions such as strconv.Itoa() or fmt.Sprint() is out of the question, as calling those functions cannot be part of a constant expression, so the result could only be used in a variable declaration (not to mention we couldn't use iota, it's only allowed in constant declarations).

But there is still a solution.

I don't think it is worth the hassle and the loss of readability, but actually you can define string constants holding increasing decimal numbers using iota.

The solution builds the "complete" numbers from digits. We can obtain the base-10 string representation by concatenating the digits (as string values) of the number.

Last question to solve for this is how to "list" the digits of a number. This is simple arithmetic:

  • The last digit (in base 10) of a number is i % 10.
  • The preceding digit is i / 10 % 10.
  • The one before that is i / 100 % 10.
  • And so on...

And to obtain the rune for a digit (which is in the range of 0..9), we can simply add '0' to it, and convert it to string. And that's all.

This is how we can code this for a 1-digit string number:

n0 = string('0'+iota%10)

For a 2-digit number:

n00 = string('0'+iota/10%10) + string('0'+iota/1%10)

For a 3-digit number:

n000 = string('0'+iota/100%10) + string('0'+iota/10%10) + string('0'+iota/1%10)

Let's see it in action:

const (
    P00 = string('0'+iota/10%10) + string('0'+iota/1%10)
    P01
    P02
    P03
    P04
    P05
    P06
    P07
    P08
    P09
    P10
    P11
    P12
    P13
    P14
    P15
    P16
    P17
    P18
    P19
    P20
)

Printing the results:

fmt.Printf("%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n%v\n",
    P00, P01, P02, P03, P04, P05, P06, P07, P08, P09,
    P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20)

Output (try it on the Go Playground):

00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20

So far so good, but how do we make it start at 3333?

Also not a problem, can be achieved easily. We can shift the iota, simply by adding an "initial" number to it. And that's all it takes.

Let's see an example where the first number will be 3339:

const (
    P3339 = string('0'+(iota+3339)/1000%10) +
        string('0'+(iota+3339)/100%10) +
        string('0'+(iota+3339)/10%10) +
        string('0'+(iota+3339)/1%10)
    P3340
    P3341
)

func main() {
    fmt.Println(P3339)
    fmt.Println(P3340)
    fmt.Println(P3341)
}

Output of the above is the expected (try it on the Go Playground):

3339
3340
3341
icza
  • 389,944
  • 63
  • 907
  • 827
  • 1
    That's clever. I don't know that I'll actually use it, because as you say it's not very readable, but you *did* answer the question I asked and obviously put a fair amount of effort into it. So consider it accepted. – Mike Ellis Sep 06 '18 at 17:02
  • @MikeEllis Made some quite significant edits, making explanation, reasoning and code much cleaner and simpler. – icza Sep 07 '18 at 08:25
1

You're creating untyped numeric constants. When in doubt, check the spec. To create a string with host and port number, you can simply use fmt.Sprintf like so:

package main

const (
    FirstPort = iota+3333
    SecondPort
    ThirdPort
)

func main() {
    hostPort := fmt.Sprintf("localhost:%d", FirstPort)
    fmt.Println(hostPort)
}

That's all there's too it: Demo

Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149