Skip to content

Closures capture scope is confusing, leading to unexpective behaviour #23684

@Alogani

Description

@Alogani

Description

Hello,

There can be multiple scopes inside a same functions thanks to blocks and templates. But this scope aren't captured as is when creating a closure. This can result in pointing to the wrong variable inside the closure.

I think an example will be more explicit :

type MyRef = ref object
var myClosures: seq[proc()]

echo "DECEPTIVE EXAMPLE - with template"
template generateClosure(): proc() =
    let myRef = MyRef()
    echo "OUTSIDE:", cast[int](myRef)
    proc closure() =
        echo "INSIDE:", cast[int](myRef)
    closure

for i in 0..<2:
    myClosures.add generateClosure()
for p in myClosures:
    p()
myClosures.setLen(0)

echo "DECEPTIVE EXAMPLE - direct substitution"
for i in 0..<2:
    myClosures.add block:
        let myRef = MyRef()
        echo "OUTSIDE:", cast[int](myRef)
        proc closure() =
            echo "INSIDE:", cast[int](myRef)
        closure

for p in myClosures:
    p()
myClosures.setLen(0)


echo "WORKING EXAMPLE"
for i in 0..<2:
    myClosures.add block:
        proc closureGenerator(): proc() =
            let myRef = MyRef()
            echo "OUTSIDE:", cast[int](myRef)
            proc closure() =
                echo "INSIDE:", cast[int](myRef)
            closure
        closureGenerator()

for p in myClosures:
    p()

Nim Version

On fedora, v2.0.4

Current Output

DECEPTIVE EXAMPLE - with template
OUTSIDE:139977855484000
OUTSIDE:139977855484096
INSIDE:139977855484096
INSIDE:139977855484096

DECEPTIVE EXAMPLE - direct subsitution
OUTSIDE:139977855484064
OUTSIDE:139977855484192
INSIDE:139977855484192
INSIDE:139977855484192

WORKING EXAMPLE
OUTSIDE:139977855484256
OUTSIDE:139977855484288
INSIDE:139977855484256
INSIDE:139977855484288

Possible Solution

I'm not sure if it is possible to capture a nested scope correctly (blocks/templates) without implying a closure. So I don't think a correction is possible without overhead or breaking how nim actually works.

However, it might certainly be possible for the compiler to detect when a referenced scope will be erased and should emit a warning or an error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions