Caveats
That's not tuple initialization
What is the output of this program?
let (a, b) = (1, 2) echo (a, b) let x, y = (1, 2) echo (x, y)
It is:
(1, 2) ((1, 2), (1, 2))
The second let does not deconstruct (1, 2), but rather is a shorthand for assigning it to both variables. This is similar to the more familiar var x, y: int syntax that gives two variables the same type.
Nim now has an EachIdentIsTuple warning for the above case. There's no such warning for the following program. In it, are the printed addresses the same?
type
Odd = object
id: int
proc `=copy`(dest: var Odd; source: Odd) {.error.}
let x, y = Odd(id: 2)
echo x
echo y
echo cast[int](x.unsafeAddr)
echo cast[int](y.unsafeAddr)They are not. Sample output:
(id: 2) (id: 2) 4347016 4347024
This syntax then is not constructing a single object for both variables, but is just a shorthand for the following:
let x = Odd(id: 2) let y = Odd(id: 2)
For loops have a similar difference between x, y and (x, y):
let a = [(1, 2), (3, 4), (5, 6)] for x, y in a: echo (x, y) echo "--" for (x, y) in a: echo (x, y)
This has the following output, showing that the first loop is binding x to successive indices of the array, and y to the members of the array, whereas the second loop is deconstructing the members of the array:
(0, (1, 2)) (1, (3, 4)) (2, (5, 6)) -- (1, 2) (3, 4) (5, 6)
And of course you can do both: for i, (x, y) in a: ...
Default returns
What does the following program print at runtime?
type
Node = ref object
case kind: range[0..1]
of 0: onedata: int
of 1: twodata: bool
proc get[T](node: Node): T =
case node.kind
of 0:
when T is string: return $node.onedata
else: assert(false)
of 1:
when T is string: return $node.twodata
else:
var a: int
for n in 0 ..< node.kind:
a.inc n
echo get[seq[int]](Node(kind: 1, twodata: true))It prints @[], the default value of a seq[int].
With --experimental:strictDefs two warnings are added to the procedure:
proc get[T](node: Node): T =
case node.kind # Warning: Cannot prove that 'result' is initialized. This will become a compile time error in the future. [ProveInit]
of 0:
when T is string: return $node.onedata
else: assert(false)
of 1:
when T is string: return $node.twodata
else:
var a: int
for n in 0 ..< node.kind:
a.inc n # Warning: use explicit initialization of 'a' for clarity [Uninit]Constant style
Why can't the following program compile?
import std/[net, strformat]
const PORT = 4444
var
server, client: Socket
address: string
server = newSocket()
server.setSockOpt(OptReusePort, true)
server.bindAddr(Port(PORT))
server.listen
while true:
server.accept(client)
let (address, port) = client.getPeerAddr
echo &"Client connected from: {address}:{port}"
client.send "Hello, world!\n"
client.closeIf you don't see it, does the error help?
/path/to/style.nim(11, 21) Error: attempting to call routine: 'Port' found 'PORT' [const declared in /path/to/style.nim(3, 7)] found nativesockets.Port [type declared in /path/to/Nim/lib/pure/nativ esockets.nim(59, 3)]
The problem is the constant PORT conflicts with the function Port, because Nim is style insensitive past the first character of an identifier. So this convention of putting constants in all caps, it doesn't really suit Nim, as all that it's done here is inflict on the reader the illusion of the code not having this name conflict.
What other options are there?
option 1: fake namespacing in the style of C symbols
const cfgPort = 4444
This seems to be the route followed in Nim internals and in the stdlib. For File I/O there are fmRead, fmWrite. For networking there's that OptReusePort option.
option 2: inconsistently renaming things when you notice a conflict
const PORT_NO = 4444
This was my first impulse, before I wondered what the point was again of putting this in all caps.
option 3: real namespacing with pure enums?
type
Config {.pure.} = enum
Port = 4444
...
server.bindAddr(Port(Config.Port))This would work for the exact code above, but just by reading through the manual on Enums it should be clear how limiting and annoying this would be in practice. You'd have to reorder your definitions if changing one's value caused it to change its order in the enum, and you're limited to integer values. Also, when even 'pure' enum names don't conflict with another identifier, they'll still be accessible without the Config. prefix:
type
Example {.pure.} = enum
Apple = 1
Orange = 2
let Apple = 10
echo Apple # output: 10
echo Orange # not an error. output: Orangeoption 4. real namespacing with a constant object
type
configtype = object
port: int
greeting: string
const Config = configtype(
greeting: "Hello, world!\n",
port: 4444,
)This occurred to me later, but it seems like a completely satisfactory solution, and one that lends itself to other uses of the object type.
'Fake namespacing' seems like the best option.
Indentation: Nim ain't Python
What does the following Python program output at runtime?
greeting = "Hello"
.upper()
.lower()
print(greeting)
It fails with an error:
File "dotty.py", line 2
.upper()
IndentationError: unexpected indent
To continue an complete-looking line onto another line, Python needs the line to look incomplete again, with \ to-be-continued markers.
In Nim though, the equivalent code runs without error:
import strutils let greeting = "Hello" .toUpper .toLower echo greeting