0

I am trying to use data from a text file containing json data. From various sources, I was able to successfully read an array of data from the file. However, when I try to use the code outside of the function in another do, try, catch block it complains about "variable xxx used before being initialized"

Looking at an answer posted here, said to create the variable outside the do, try, catch block but it just moves the error.

swift 5 (5.7.1)

cli learning app

units.json

This is the sample json file containing an array of elements.

{
    "units": [
        {
            "name":"cartman",
        },
        {
            "name":"kyle",
        }
    ]
}

fileIO.swift

This is the code to do fileIO

import Foundation


// src https://stackoverflow.com/a/36827996/1008596
struct UnitData: Decodable {
    var name: String = "none"
}

struct UnitsData: Decodable {
    var units: [UnitData]
}



enum JFDFileError: Error {
    case FileNotFound
    case JSONDecodeFailed
}


// src https://www.swiftbysundell.com/articles/working-with-files-and-folders-in-swift/
func my_simple_file_read(path: String) throws -> UnitsData {
        
    let file_name = (path as NSString).expandingTildeInPath
    let url = URL(fileURLWithPath: file_name)
    
    // Read the file
    var data: Data
    do {
        data = try Data(contentsOf: url)
    } catch {
        throw JFDFileError.FileNotFound
    }

    var unitsData: UnitsData
    do {
        let decoder = JSONDecoder()
        unitsData = try decoder.decode(UnitsData.self, from: data)
        // these prints work
        // prints entire json file
        print("entire json file \(unitsData)")
        // prints specific element of json array
        print("this works 0 is \(unitsData.units[1].name)")
    } catch {
        throw JFDFileError.JSONDecodeFailed
    }
    // this print works
    print("this works here \(unitsData.units[1].name)")
    return unitsData
    
}

main.swift

This is an excerpt of main code. It has same form as function but return of function call fails here. Why?

   // Read units from json file
    var unitsData: UnitsData
    do {
        unitsData = try  my_simple_file_read(path: "~/somepath/units.json")
        // this works
        print("works1 is \(unitsData.units[1].name)")
        
    } catch {
        print("an error occured")
    }
    
    // error: Variable 'unitsData' used before being initialized

    print("does not work \(unitsData.units[1].name)")
netskink
  • 4,033
  • 2
  • 34
  • 46
  • Move the `var unitsData: UnitsData` line inside the `do` block. Move any code after the whole `do/catch` block inside the `do` block. – HangarRash Feb 24 '23 at 15:34
  • if I do that, all the code which requires on the initialization data read from the file has to be inside that do block. Is there a way to write some stubs so it could pass the compiler init check? – netskink Feb 24 '23 at 15:39
  • Rob, the error is a compile error. It's not a runtime error. FWIW, there is another catch block in the function my_simple_file_read() for catching runtime errors. – netskink Feb 24 '23 at 15:41
  • No worries, Rob. And I appreciate your response. FWIW, I tried your suggestion, but removing do and catch with just try will not compile. Removing the try and it also fails to compile. It says on the decoder.decode line, "Call can throw but it is not marked with 'try' and the error is not handled." – netskink Feb 24 '23 at 15:47
  • Rob, fwiw, I rewrote the error enum, to reflect a little more on the error handling. – netskink Feb 24 '23 at 15:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/252109/discussion-between-rob-and-netskink). – Rob Feb 24 '23 at 15:52

3 Answers3

2

Regarding the compile-time error in Main.swift:

Variable 'unitsData' used before being initialized

That is because you have a path of execution (where the try my_simple_file_read could throw an error) whereby unitsData variable is not set. All paths of execution must set the variable, or else the compiler will generate an error like you have here.

Either refrain from referencing unitsData after the do-catch statement (i.e., put all unitsData references inside the do block), or exit if an error is caught, or make unitsData an optional and set it to nil in the catch block.

The difference in the function implementation, is that you have an “early exit” if you catch an error (in your catch statement you throw, which exits immediately), and you don’t get to that final print statement in the function. Thus there is no use of a variable before it has been initialized.

That contrasts to the Main.swift example, in which your catch statement merely prints a message and continues execution, reaching a reference to unitsData that was never set. The compiler detects this misuse and generates that compile-time error.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

First of all John Sundell is a serious and honorable Swift developer. He would never suggest snake_case function names like my_simple_file_read( or discarding errors by printing meaningless string literals.

Basically to declare a variable without a default value makes only sense if it's guaranteed that the variable is initialized somewhere. For example

var unitsData: UnitsData
do {
    unitsData = try my_simple_file_read(path: "~/somepath/units.json")
    // this works
    print("works1 is \(unitsData.units[1].name)")
    
} catch {
   unitsData = UnitsData(units: [])
   print(error)
}

The compiler throws an error because you don't assign a value in the catch block.


But in your case declare and initialize the variable constant inside the do scope

do {
    let unitsData = try my_simple_file_read(path: "~/somepath/units.json")
    print("works1 is \(unitsData.units[1].name)")
    
} catch {
    print(error)
}

If the function throws anyway a better implementation is

func mySimpleFileRead(path: String) throws -> UnitsData {
        
    let fileName = (path as NSString).expandingTildeInPath
    let url = URL(fileURLWithPath: fileName)
   
    let data = try Data(contentsOf: url)
    return try JSONDecoder().decode(UnitsData.self, from: data)
}

The thrown errors by the framework are more significant than your custom errors.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Hello Vadian, can you chat here? https://chat.stackoverflow.com/rooms/252109/discussion-between-rob-and-netskink – netskink Feb 24 '23 at 16:03
0

I learned a few things today:

  • There is a chat option which can be used. With that said, Rob and Vadian, helped me interactively.
  • The reason, the print outside the do catch block worked in the fileIO function but not in the main code which called the function was because I had a simple print in the catch block and did not rethrow an error. This meant in case of an error, the structure was not initialized and at runtime the array would be undefined. Amazing that the compiler could differentiate that.

With that said, is the solution and it demos the better way to write the initial function and the original function to show why it worked one way but not the other. ie. worked in original code with throw but not print.

file.swift

func my_simple_file_read(path: String) throws -> UnitsData {

    print("path is \(path)")
    
    let file_name = (path as NSString).expandingTildeInPath
    print("file_name is \(file_name)")

    
    let url = URL(fileURLWithPath: file_name)
    print("url is \(url)")
    
    // Read the file
    var data: Data
    do {
        data = try Data(contentsOf: url)
        print("data is \(data)")
    } catch {
        throw JFDFileError.FileNotFound
    }

    var unitsData: UnitsData
    do {
        let decoder = JSONDecoder()
        unitsData = try decoder.decode(UnitsData.self, from: data)
        print("json data is \(unitsData)")
        print("json data.name is \(unitsData.units[1].name)")
    } catch {
        throw JFDFileError.JSONDecodeFailed
    }




    print("===.name is \(unitsData.units[1].name)")
    return unitsData
    
}


func mySimpleFileRead(path: String) throws -> UnitsData {
        
    let fileName = (path as NSString).expandingTildeInPath
    let url = URL(fileURLWithPath: fileName)
   
    let data = try Data(contentsOf: url)
    return try JSONDecoder().decode(UnitsData.self, from: data)
}

main.swift

func doThing(id1:Int, id2:Int) throws -> (Int, Int) {
    print("-----")

    // Read units from json file
    var unitsData: UnitsData
    do {
        unitsData = try  my_simple_file_read(path: "~/somepath/units.json")
        print("=++++==.name is \(unitsData.units[1].name)")

        
    } catch {
        throw JFDFileError.JSONDecodeFailed
    }
    
    print("1=-+-+==.name is \(unitsData.units[1].name)")

    var unitsData2: UnitsData
    do {
        unitsData2 = try mySimpleFileRead(path: "~/progs/myxcode/ogrev1/ogrev1/ogrev1/units.json")
    } catch {
        throw JFDFileError.JSONDecodeFailed
    }
    
    print("2=-+-+==.name is \(unitsData2.units[1].name)")
...
...
...
netskink
  • 4,033
  • 2
  • 34
  • 46