قالب وردپرس درنا توس
Home / IOS Development / mikeash.com: Friday Q & A 2017-08-25: Swift Error Action Implementation

mikeash.com: Friday Q & A 2017-08-25: Swift Error Action Implementation



Friday Q & A 2017-08-25: Swift Error Action Implementation

This article is also available in Korean (translation of pilgwon).

Swift's error handling is a unique feature of the language. It looks great as an exception in other languages, but the syntax is not quite the same, and it does not work either. Today I'll look at how Swift errors work inside.

Semantics
Let's start with a quick update on how Swift errors work at the language level.

Any Swift feature can be decorated with casts keywords that indicate that it may cast an error:

      func    getStringMightFail   ()    throws    ->    String    {   ... 

To actually cast an error from such a function, use cast the keyword with a value corresponding to the protocol Error Error ]:

          caste [1
9659010] MyError
. brainNotFound

When calling a throwing function, you must include try try the keyword:

      la    string    =    try    try    ] getStringMightFail   () 

try The keyword does nothing but is a necessary cursor to indicate that the function may cast an error. The conversation must be in a context where it is allowed to cast an error, either in a cast feature or in a making block with a capture trader. [19659006] To write a catch place try the call in a do block, and add a capture block: 19659013] {
string = sample getStringMightFail ()
...
} catch {19659010]
Print ( "Found an error: (error)" )
}

When an error is thrown, the drive to captures the block . The value thrown is available in error . You can get fancy with type of checks and conditions and more Capture clauses, but this is basic. For more information about all details, see the Error Handling section in the swift programming language.

That's what's being done. How does it work?

Implementation
To find out how it works, I wrote some dummy code with error management that I could disassemble:

      struct    MyError :    Error    Error    Error    Error    ]    var    x :    Int 
          was    y :    Int 
          was    z :    Int 
    ] 

      FUNC    Casting   (  x :    Int     y :    Int  [19659051] z :    Int )    cast    ->    Int    {
          caste    MyError   (  x  : [19659017]  :    z ) 
    } 

      FUNC [19659010] Catcher  :    19659000] [ ] :    (  Int     Int     Int ) [1 9659010] throws    ->    Int )    {[19659120] where    {
              la    x    =    sample    f   ]   1    2    3 ) 
              print   (  "Received  (x)"  ] 
        }    capture    {
              print   (  "Capture  (error)" ) 
        } 
    } 

Of course now that Swift is open source, I could only go to the compiler code and see what it does. But it's not fun and this is easier.

It turns out that Swift 3 and Swift 4 make it different. I will briefly discuss Swift 3, so look a bit deeper on Swift 4, since it comes and comes.

Swift 3 works by automating Objective-Cs NSError convention. The compiler will insert an additional hidden parameter that is essentially Error * or NSError ** . To cast an error consists in writing the wrong object to the pointer passed in that parameter. The caller assigns a bit of space and sends the address in that parameter. Upon return, it checks to see if this space now contains an error. If it does, it jumps to the block .

Swift 4 becomes a little more advanced. The basic idea is the same, but instead of a normal extra parameter, a special registry is reserved for the error message. Here is what the relevant assembly code in Thrower looks like:

      ring          imp ___ stubs__swift_allocError 
      mov           qword    [  rdx ]    ] 
      mov           QWORD    [  RDX   +   8 ]    r15 
      mov           QWORD    [  RDX [19659163] r12     rax 

This invokes Swift runtime to assign a new error, fills it with the appropriate values, and then places the pointer in r12 . It returns to the caller. r12 r12 r12
u loc_100002cec

It calls the call and checks if r12 contains something. If it does, it jumps to the block catch . The technique of ARM64 is almost the same, with x21 the register as the error pointer.

Internally, it seems like returning a Result type, or otherwise returning some sort of error code. The function throws returns the cast of the caller to a particular location. The caller checks the location of an error and jumps to the error handling code if that is the case. The generated code resembles the objective C code using a parameter NSError ** and in fact Swift 3's version of the same is.

Comparison with exception
Swift is careful not to use the word "exception" when discussing the error management system, but it seems like exceptions in other languages. How does its implementation compare? There are many languages ​​out there except, and many of them do things differently, but the natural comparison is C ++. Lens-C exceptions (which exist, although virtually no-one uses them) uses the C ++ exception mechanism at modern runtime.

A complete survey of how C ++ exceptions work can fill a book so we have to pay for a brief description.

The C ++ code that calls caste features (which is the default for C ++ functions) produces assembly exactly as if it called non-casting functions. That is, it goes into parameters and retrieves return values ​​and gives no reason for the possibility of exceptions.

How could this possibly work? In addition to generating the exception code, the compiler also generates a table of information about how (and about) the code handles exceptions and how to safely relax the stack to exit the function in case an exception is thrown.

When some features cast an exception, it looks up in the stack, looks up every function information, and uses it to relax the stack to the next function until it finds an exception handler or ends. If it finds an exception handler, it transfers the control to the handler running the code in the block .

For more information about how C ++ exceptions work, see C ++ ABI for Itanium: Exception Action.

This system is called "exception" exception handling. The term "zero price" refers to what happens when no exceptions are thrown. Because that code is compiled just as it would be without exception, there is no runtime overhead to support exceptions. Calling potentially throwing functions is as quick as calling non-throwing features, adding try blocks to your code does not lead to any extra work over time.

When an exception is discarded the term "zero price" is thrown out of the window. Wrapping the stack using the tables is an expensive process and takes a considerable amount of time. The system is designed around the idea that exceptions are rarely thrown, and performance in case no exception is thrown is more important. This assumption is likely to be true in almost all code.

Compared to this, Swifts system is extremely simple. There is no attempt to generate the same code for throwing and not throwing functions. Instead, all calls to a cast the function followed by a check to see if an error was returned, and a jump to the correct error handling code if that is the case. These controls are not free, although they should be quite cheap.

The deviation gives a lot of meaning to Swift. Swift errors look very similar to C ++ exceptions, but in practice they are used differently. Almost all C ++ calls can potentially throw, and even basic things like the new operator will throw to indicate an error. Explicitly check for a cast exception after each conversation will add many additional checks. In contrast, few Swift calls marked throw into typical codebases, so the price of explicit checks is low.

Conclusion
Swift troubleshooting invites you to compare with exceptions in other languages, such as C ++. The C ++ exception management is extremely complicated internally, but Swift takes a different approach. Instead of relaxing tables to achieve "zero price" in the usual case, Swift returns throwback in a special register, and the caller registers to see if an error has been thrown. This adds a little overhead when errors are not thrown, but avoids making things very complicated as C ++ does. It would take a lot of effort to write swift code where overhead from error handling makes some noticeable difference.

That's it for today! Come back again for more excitement, fun and horror. As I mentioned earlier, Friday Q & A is powered by readers' suggestions. As always, if you have a subject you want to see covered here, submit it!

Do you like this article? I sell all the books full of them! Volumes II and III are now out! They are available as ePub, PDF, Print, and on iBooks and Kindle. Click here for more information.


Comments:


Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off topic posts are deleted without notice. Culprits can be humiliated publicly in my own discretion.

Code syntax highlighting thanks to Pygments.

Source link