3

I have a global variable of audio player. What's the difference between placing try word before the variable initialization

do{
   try audioPlayer = AVAudioPlayer(contentsOf: audioURL)
}catch {}

and placing try before calling the constructor

do{
   audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
}catch {}

Of course two cases above haven't any compilation errors. Thanks

GeRyCh
  • 1,580
  • 3
  • 13
  • 23
  • I would suggest that first case tries assigning and second tries initiating. But I am not sure, just guess. – JuicyFruit Mar 09 '17 at 11:28

2 Answers2

5

There is no practical difference. As said by the language guide (emphasis mine):

When the expression on the left hand side of a binary operator is marked with try, try?, or try!, that operator applies to the whole binary expression. That said, you can use parentheses to be explicit about the scope of the operator’s application.

// try applies to both function calls
sum = try someThrowingFunction() + anotherThrowingFunction()

// try applies to both function calls
sum = try (someThrowingFunction() + anotherThrowingFunction()) 

// Error: try applies only to the first function call
sum = (try someThrowingFunction()) + anotherThrowingFunction()

Just like +, assignment is also a binary operator. Therefore, when you say

do{
   try audioPlayer = AVAudioPlayer(contentsOf: audioURL)
}catch {}

The try applies to both expressions audioPlayer and AVAudioPlayer(contentsOf: audioURL). The lone expression audioPlayer cannot possibly throw an error here – therefore the try in this case only applies to the call to AVAudioPlayer's init(contentsOf:), which can throw.

The derivation of the grammar for this is:

//           "try"          "audioPlayer"     "= AVAudioPlayer(contentsOf: audioURL)"
expression → try-operator­opt ­prefix-expression ­binary-expressions­opt

prefix-expression → prefix-operator­opt ­postfix-expression
postfix-expression → primary-expression­
primary-expression → identifier­ generic-argument-clause­opt
identifier­ → // matches "audioPlayer" (I'm not going to fully derive this bit further)

binary-expressions → binary-expression ­binary-expressions­opt
binary-expression → assignment-operator ­try-operator­opt­ prefix-expression
prefix-expression → prefix-operator­opt postfix-expression
postfix-expression → initializer-expression­ // matches AVAudioPlayer(contentsOf: audioURL)

When you say

do{
   audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
}catch {}

You're using the fact that the assignment expression has the grammar of:

binary-expression → assignment-operator ­try-operator­opt ­prefix-expression­

As you can see, the try-operator can also appear on the right hand side of the operator here, and therefore will apply to the prefix-expression – which in this case is AVAudioPlayer.init(contentsOf:).

So in both cases, you're catching the error potentially thrown from AVAudioPlayer.init(contentsOf:). The first example just includes the possibility of an error being thrown from the expression on the left hand side of the operator, which it cannot possibly do.

Use whichever you feel more comfortable with – my personal preference, and the option which is more consistent with the placement of try in other places in the language, is to put the try on the right-hand side.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 3
    Great explanation. Those coming from languages with exceptions may assume that `try` has some deep significance, marking where a longjmp goes or other "exception" implementation detail. It's useful to keep in mind that Swift exceptions are just a fancy version of `return`. The `try` has no impact on the compiler or code generation. It's purely there to remind the programmer that this block of code may throw exceptions. Once the compiler feels the `try` is close enough to provide that reminder, it's happy. :D – Rob Napier Mar 09 '17 at 13:52
  • Actually this is not quite true. There are situations where a `try` at the start of the line will not compile. I think the fact that it ever does compile is a bug. – matt Mar 09 '17 at 14:20
2

In my view, the fact that this compiles is kind of a bug:

try audioPlayer = AVAudioPlayer(contentsOf: audioURL)

Basically, you're doing something you shouldn't do, and you're just lucky that it slides past the compiler here. Consider, as a counterexample, what would happen if audioPlayer were being declared here:

try let audioPlayer = AVAudioPlayer(contentsOf: audioURL) // error

That doesn't compile, and the Fix-It correctly moves the try to where it belongs:

let audioPlayer = try AVAudioPlayer(contentsOf: audioURL)

The rule that I recommend following, therefore, is that you should use try placement correctly — it modifies the method call that can throw. It may sometimes work for an assignment as a whole, but not always, and it's a bad habit to get into.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • That's a slightly different thing though – that's a binding. If you take a look in the [grammar for a constant declaration](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355), there's no room for a `try` until the expression on the right hand side of the `=`. Although I agree that the rules are inconsistent, and that `try` on the rhs is preferable :) – Hamish Mar 09 '17 at 14:41
  • @Hamish Right, I'm just trying to inculcate the notion that "`try` at the start of the line" is _not_ generally okay. If I thought it would do any good, I'd file a bug on this, but I know it wouldn't. :) – matt Mar 09 '17 at 14:43
  • 1
    `try` always prefixes an *expression* (not a method call). That's why it's legal to prefix the assignment which is an expression of type `()` or `Void`. It is not necessary for `try` to immediately prefix the throwing part of an expression. Your example `let audioPlayer = ...` is a declaration and so cannot be marked with a `try`. – Nikolai Ruhe Mar 09 '17 at 16:10
  • @NikolaiRuhe I'm with you, but I'm saying it would be a good habit if the expression in question were the expression immediately containing the method call, rather than the whole assignment. – matt Mar 09 '17 at 16:11
  • I agree that it's good style to make the try scope as small as possible. I wanted to make clear that 1. it's not a bug and 2. try does not modify individual method calls. – Nikolai Ruhe Mar 09 '17 at 16:16