nim-lang / RFCs

A repository for your Nim proposals.
135 stars 26 forks source link

Definite assignment analysis for uninitialized `let` #495

Closed metagn closed 1 year ago

metagn commented 1 year ago

Abstract

Allow let statements to not have an initial value, but enforce definite assignment analysis on them, probably by requiring the experimental feature be turned on.

Motivation

This is mostly quality of life, for example we might have a variable that we want to initialize through some complex statement but don't want to mutate again:

var x: string
if true:
  x = "abc"
else:
  x = "def"
# shouldn't mutate x again

Currently we have to do something like this:

let x = block:
  var xTemp: string
  if true:
    xTemp = "abc"
  else:
    xTemp = "def"
  xTemp

But this is bloated and relies on destructors to work well.

Description

An alternative solution to the above situation is by allowing shadowing, i.e. let x = x, which may not be preferable for multiple reasons that I don't think are necessary to go into.

The syntax let x: T with no initializer is already allowed for importc variables. On the other hand, this syntax is not allowed for const. Part of the reason that this is disallowed for const is that it makes compilation and analysis way more straightforward (from what I remember). However the same problem does not really apply to let, as anything that handles let probably also has to handle var which is even less predictable.

Code Examples

let x: string
if true:
  x = "abc"
else:
  x = "def"
echo x
proc foo(x: out string) =
  x = "abc"

let x: string
foo(x)
echo x

Backwards Compatibility

The worst possibility is that old macros might not be able to handle code that uses this, but since no old code has this syntax, no old code that uses those macros will break either. I would imagine most macros would be indifferent to it though, or would give a custom error saying "let statement needs initializer".

ghost commented 1 year ago

FWIW, a small nitpick about the current let example - you don't actually have to use a block:

let x = 
  if true:
    "abc"
  else:
    "def"

This is still a bit uncomfortable (extra indentation) of course.

metagn commented 1 year ago

Oops, had a more complex example in mind but oversimplified it lol. These are some examples:

var x: string
if true:
  x = "abc"
  doSomething(x)
else:
  x = "def"
  doSomethingElse(x)

# has to be

let x = if true: "abc" else: "def"
if true:
  doSomething(x)
else:
  doSomethingElse(x)
var x: T
if true:
  open1(x)
else:
  open2(x)

# have to use temp

let x = block:
  var xTemp: T
  if true:
    open1(xTemp)
  else:
    open2(xTemp)
  xTemp

This one isn't bad but it's a contrast to the one after it:

var a, b: int
if true:
  a = 1
  b = 2
else:
  a = 3
  b = 4

# has to be

let (a, b) = if true: (1, 2) else: (3, 4)
var a, b: int
if true:
  a = 1
  b = foo(a)
else:
  a = 3
  b = bar(a)

# has to be
let a = if true: 1 else: 3
let b = if true: foo(a) else: bar(a)
Araq commented 1 year ago

I love it, it's one of these things "why didn't we do it this way from the beginning".