nim-lang / RFCs

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

`block` should not interfere with `break` #451

Closed simonkrauter closed 1 year ago

simonkrauter commented 2 years ago

Abstract

block: should have no effect to the break statement and behave the same than if true:.

Motivation

From other programming languages (C, C++) I am used, that introducing a new block with { and } will not change the behavior of a break inside the block.

C++ example:

#include <iostream>

int main()
{
  for (int i = 1; i <= 10; i++)
  {
    std::cout << i << std::endl;
    { // new block
      if (i == 5)
          break; // leaves the loop
    }
  }
  return 0;
}

Nim example:

for i in 1..10:
  echo i
  block:
    if i == 5:
      break # does not leave the loop

In my opinion, Nim's behavior is against the Principle of Least Surprise.

It happend to myself several times, that I thought I can use the block keyword the same way than { and } in C/C++ to define a new variable scope.

Description

Status quo: The break statement considers a block created by block: different to a regular block created for example by if true:. A block created by block: affects the control flow, while a regular block does not.

From the manual:

The block statement is a means to group statements to a (named) block. Inside the block, the break statement is allowed to leave the block immediately. A break statement can contain a name of a surrounding block to specify which block is to be left.

I propose to remove the interference between block and break, when the block is not explicitly referenced by it's name.

Examples

The following two examples are expected to work in the same way.

block: # example 1:
  var i = 1
  while i <= 10:
    echo i
    if true:
      if i == 5:
        break
    i.inc
  assert i == 5

block: # example 2:
  var i = 1
  while i <= 10:
    echo i
    block:
      if i == 5:
        break
    i.inc
  assert i == 5 # fails with the current Nim version

Backward incompatibility

Existing code would break. I have no idea for a backward-incompatible solution.

konsumlamm commented 2 years ago

I'm not sure this is a good idea. It seems like you're not familiar with named blocks (block a: ...), which can be used for labeled breaks (break a), so that you can for example break from a nested loop. Having unnamed blocks create a "break boundary" is consistent with that.

As for your motivation, there's really no point in creating a new scope (i.e. using bock) here.

Araq commented 2 years ago

You raise a good point, but a "block" is not a "scope". We could add a "scope" construct:


template scope(body) =
  if true:
    body

There are already similar constructs in Nim's sugar.nim.

mratsim commented 2 years ago

No for me. The benefit doesn't outweight the breakage that will ensue especially with no backward compatible solution.

@Araq Scoping needs non-dirty templates no?

Araq commented 2 years ago

@Araq Scoping needs non-dirty templates no?

No. Templates don't add/subtract scopes, there is .gensym for symbol duplication (and .dirty to inhibit it) but that's orthogonal to scoping.

juancarlospaco commented 2 years ago

Nope, it is even more confusing.

xigoi commented 2 years ago

What's the point of {.dirty.} here if you're not introducing new identifiers?

Araq commented 2 years ago

What's the point of {.dirty.} here if you're not introducing new identifiers?

There is none.

Araq commented 1 year ago

It is coming, see https://github.com/nim-lang/Nim/pull/20901