Bug in forward class reference?

I have the following code:

import Foundation

class HasRecursive
{
   var i:Int
   var hr:HasRecursive?
   
   init()
   {
      i  = 0
      hr = nil
   }
   
   init( i:Int, hr:HasRecursive? )
   {
      self.i  = i
      self.hr = hr
   }
   
   func create( i:Int, hr:HasRecursive? )
   {
      self.i  = i
      self.hr = hr
   }
   
   func printInt()
   {
      if hr != nil
      {
         hr!.printInt()
      }
      print( i )
   }
}

let hr1 = HasRecursive( i:1, hr:hr2 )
let hr2 = HasRecursive( i:2, hr:nil )

print( "hr1:" )
hr1.printInt()

let hr3 = HasRecursive()
let hr4 = HasRecursive()
hr3.create( i:1, hr:hr4 )
hr4.create( i:2, hr:nil )

print( "hr3:" )
hr3.printInt()

The output is:

hr1:
1
hr3:
2
1

The statement:

let hr1 = HasRecursive( i:1, hr:hr2 )

sets the hr stored property field to nil because hr2 has yet to be declared. As a result, the call to hr1.printInt() only displays "1", not "2" as you would logically expect. Of course, as hr2 does not yet have a value, this is understandable. However, I feel that the compiler should report an error here ("hr2 is not yet initialized") rather than just assigning nil to the hr field.

The only work-around I've found is to write a separate function to initialize the fields ("create(--)" in this example) and run that function after creating all the related object instances. Is there a better way to do this? (in general, obviously I could rearrange the declarations and fix this issue in this particular case, but that's not always possible in the general case).

Global variables have lazy semantics. Here's a simpler repro:

let b = a
let a = 1

print(b)

This becomes useful when dealing with multiple files, and there's no definite order in which they're evaluated. Contrast this with say, C, where you need to manually import header files and their order matters

Are you working in a playground? The code you wrote cannot compile. I recommend always working in a Swift package; playgrounds are misleading.

1 Like

It compiles successfully in a standalone file:

$ cat > demo.swift <<EOF
let b = a
let a = 1
print(b)
EOF

$ swift demo.swift # prints 1
let b = a
let a = 1

print( "b =\(b)" )

when I run this code inside Xcode, or compile and run from the command line, I get:

b =0

which is exactly what happens to me in the original code I ran (just getting nil instead of 0).

BTW:

$ swiftc --version
swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
Target: x86_64-apple-macosx14.0

This is a bug with top level code and you are essentially running into undefined behavior.
One thread that discusses this: On the behavior of variables in top-level code

1 Like