initializer with alternative


(J.E. Schotsman) #1

Hello,

I have a class that can be initialized from a file.
I would like to add a convenience init that uses a backup file if necessary.
Writing a function for this is easy, but I can’t manage to turn this into an init function.

import Foundation

class TestClass
  {
  init( fromFile file:NSURL ) throws
    {
    
    }
  
  convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
    {
    do { try self.init( fromFile:file ) }
    catch {
      do { try self.init( fromFile:backupFile ) } // error: 'self' used inside 'catch' block reachable from self.init call
      }
    }
  }

func MakeTestClass( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws -> TestClass
  {
  do { return try TestClass( fromFile:file ) }
  catch { do { return try TestClass( fromFile:backupFile ) } }
  }

Any suggestions?


(Doug Hill) #2

If your self.init method fails by throwing an exception, the state of the class is essentially unknown. Trying to recover inside the convenience init method by calling another initializer seems problematic since you don't know what the state will be after the second init. Therefor, it's probably more correct to do the exception handling outside of this class like you did in the MakeTestClass function.

But just for the heck of it, I tried taking the second init outside the catch block, and put a flag that something failed. But Swift won't allow any uses of self after the first catch. For example (with errors on the lines the compiler reports):

convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
{
    var firstInitFailed = false

    do {
        try self.init( fromFile:file )
    } catch {
        do {
             firstInitFailed = true
    }
  }

    if firstInitFailed == false {
  try self.init( fromFile:backupFile) // 'self' used inside 'catch' block reachable from self.init call
  }
} // 'self' used inside 'catch' block reachable from self.init call
This might be a bug in the compiler since the error message isn't correct; self isn't referenced in the catch block. Also the error message on the closing brace is puzzling.

In any case, this appears to imply that in Swift you can't do any error recovery that involves self inside of an init method, even if you get an exception from something other than calling self.init. This doesn't seem right if the throw was for something that doesn't affect the object state. For example, if you had a file system call that tried to create 'fromFile' and that call threw an exception. This shouldn't affect anything with class state and should be possible to handle it without affecting object initialization.

The third option is to move this file handling outside of the init method. For example:

convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
{
    try self.init(fromFile: file)

    self.openFile(fromFile: file, fromBackupFile: backupFile)
}

func openFile( fromFile file:NSURL, fromBackupFile backupFile:NSURL )
{
    do {
        // try to open the fromFile
    } catch {
        do {
            // try to open the backupFile
        }
    }
}

However, this changes the class' init behavior since the designated initializer can't do the file opening/handling. I'm guessing your best bet is going with the separate function to handle this. Or make it a type method.

Doug Hill

···

On Jul 25, 2016, at 10:46 AM, J.E. Schotsman via swift-users <swift-users@swift.org> wrote:

Hello,

I have a class that can be initialized from a file.
I would like to add a convenience init that uses a backup file if necessary.
Writing a function for this is easy, but I can’t manage to turn this into an init function.

import Foundation

class TestClass
  {
  init( fromFile file:NSURL ) throws
    {
    
    }
  
  convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
    {
    do { try self.init( fromFile:file ) }
    catch {
      do { try self.init( fromFile:backupFile ) } // error: 'self' used inside 'catch' block reachable from self.init call
      }
    }
  }

func MakeTestClass( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws -> TestClass
  {
  do { return try TestClass( fromFile:file ) }
  catch { do { return try TestClass( fromFile:backupFile ) } }
  }

Any suggestions?


(Tod Cunningham) #3

I would probably approach it like Doug suggested and use a class method.

class TestClass
{
    required init( fromFile file:NSURL ) throws
    {
    }

    class func makeTestClass( fromFile file:NSURL, fromBackupFile backupFile: NSURL ) throws -> TestClass {
        do { return try self.init( fromFile:file ) }
        catch { do { return try self.init( fromFile:backupFile ) } }
    }
}

···

From: <swift-users-bounces@swift.org> on behalf of Doug Hill via swift-users <swift-users@swift.org>
Reply-To: Doug Hill <swiftusers@breaqz.com>
Date: Monday, July 25, 2016 at 4:41 PM
To: "J.E. Schotsman" <jeschot@xs4all.nl>
Cc: "swift-users@swift.org" <swift-users@swift.org>
Subject: Re: [swift-users] initializer with alternative

If your self.init method fails by throwing an exception, the state of the class is essentially unknown. Trying to recover inside the convenience init method by calling another initializer seems problematic since you don't know what the state will be after the second init. Therefor, it's probably more correct to do the exception handling outside of this class like you did in the MakeTestClass function.

But just for the heck of it, I tried taking the second init outside the catch block, and put a flag that something failed. But Swift won't allow any uses of self after the first catch. For example (with errors on the lines the compiler reports):

convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
{
    var firstInitFailed = false

    do {
        try self.init( fromFile:file )
    } catch {
        do {

    firstInitFailed = true
}
}

    if firstInitFailed == false {
try self.init( fromFile:backupFile) // 'self' used inside 'catch' block reachable from self.init call
}
} // 'self' used inside 'catch' block reachable from self.init call
This might be a bug in the compiler since the error message isn't correct; self isn't referenced in the catch block. Also the error message on the closing brace is puzzling.

In any case, this appears to imply that in Swift you can't do any error recovery that involves self inside of an init method, even if you get an exception from something other than calling self.init. This doesn't seem right if the throw was for something that doesn't affect the object state. For example, if you had a file system call that tried to create 'fromFile' and that call threw an exception. This shouldn't affect anything with class state and should be possible to handle it without affecting object initialization.

The third option is to move this file handling outside of the init method. For example:

convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
{
    try self.init(fromFile: file)

    self.openFile(fromFile: file, fromBackupFile: backupFile)
}

func openFile( fromFile file:NSURL, fromBackupFile backupFile:NSURL )
{
    do {
        // try to open the fromFile
    } catch {
        do {
            // try to open the backupFile
        }
    }
}

However, this changes the class' init behavior since the designated initializer can't do the file opening/handling. I'm guessing your best bet is going with the separate function to handle this. Or make it a type method.

Doug Hill

On Jul 25, 2016, at 10:46 AM, J.E. Schotsman via swift-users <swift-users@swift.org<mailto:swift-users@swift.org>> wrote:

Hello,

I have a class that can be initialized from a file.
I would like to add a convenience init that uses a backup file if necessary.
Writing a function for this is easy, but I can’t manage to turn this into an init function.

import Foundation

class TestClass
{
init( fromFile file:NSURL ) throws
{

}

convenience init( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws
{
do { try self.init( fromFile:file ) }
catch {
do { try self.init( fromFile:backupFile ) } // error: 'self' used inside 'catch' block reachable from self.init call
}
}
}

func MakeTestClass( fromFile file:NSURL, fromBackupFile backupFile:NSURL ) throws -> TestClass
{
do { return try TestClass( fromFile:file ) }
catch { do { return try TestClass( fromFile:backupFile ) } }
}

Any suggestions?


(J.E. Schotsman) #4

I'm guessing your best bet is going with the separate function to handle this. Or make it a type method.

Of course. I didn't think of the type method approach.

// 'self' used inside 'catch' block reachable from self.init call
This might be a bug in the compiler since the error message isn't correct; self isn't referenced in the catch block. Also the error message on the closing brace is puzzling.

I got this message too inside an if err {} block.
I don't think I'll file a bug as this is probably a rare case.

Jan E.

···

On Jul 26, 2016, at 4:15 PM, Doug Hill wrote: