When to use inout parameters?

前端 未结 7 1591
情书的邮戳
情书的邮戳 2020-12-13 03:15

When passing a class or primitive type into a function, any change made in the function to the parameter will be reflected outside of the class. This is basically the same t

7条回答
  •  抹茶落季
    2020-12-13 03:57

    From Apple Language Reference: Declarations - In-Out Parameters:

    As an optimization, when the argument is a value stored at a physical address in memory, the same memory location is used both inside and outside the function body. The optimized behavior is known as call by reference; it satisfies all of the requirements of the copy-in copy-out model while removing the overhead of copying. Do not depend on the behavioral differences between copy-in copy-out and call by reference.

    If you have a function that takes a somewhat memory-wise large value type as argument (say, a large structure type) and that returns the same type, and finally where the function return is always used just to replace the caller argument, then inout is to prefer as associated function parameter.

    Consider the example below, where comments describe why we would want to use inout over a regular type-in-return-type function here:

    struct MyStruct {
        private var myInt: Int = 1
    
        // ... lots and lots of stored properties
    
        mutating func increaseMyInt() {
            myInt += 1
        }
    }
    
    /* call to function _copies_ argument to function property 'myHugeStruct' (copy 1)
       function property is mutated
       function returns a copy of mutated property to caller (copy 2) */
    func myFunc(var myHugeStruct: MyStruct) -> MyStruct {
        myHugeStruct.increaseMyInt()
        return myHugeStruct
    }
    
    /* call-by-reference, no value copy overhead due to inout opimization */
    func myFuncWithLessCopyOverhead(inout myHugeStruct: MyStruct) {
        myHugeStruct.increaseMyInt()
    }
    
    var a = MyStruct()
    a = myFunc(a) // copy, copy: overhead
    myFuncWithLessCopyOverhead(&a) // call by reference: no memory reallocation
    

    Also, in the example above---disregarding memory issues---inout can be preferred simply as a good code practice of telling whomever read our code that we are mutating the function caller argument (implicitly shown by the ampersand & preceding the argument in the function call). The following summarises this quite neatly:

    If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.

    From Apple Language Guide: Functions - In-Out Parameters.


    For details regarding inout and how it's actually handled in memory (name copy-in-copy-out is somewhat misleading...)---in additional to the links to the language guide above---see the following SO thread:

    • Using inout keyword: is the parameter passed-by-reference or by copy-in copy-out (/call by value result)

    (Edit addition: An additional note)

    The example given in the accepted answer by Lucas Huang above tries to---in the scope of the function using an inout argument---access the variables that were passed as the inout arguments. This is not recommended, and is explicitly warned not to do in the language ref:

    Do not access the value that was passed as an in-out argument, even if the original argument is available in the current scope. When the function returns, your changes to the original are overwritten with the value of the copy. Do not depend on the implementation of the call-by-reference optimization to try to keep the changes from being overwritten.

    Now, the access in this case is "only" non-mutable, e.g. print(...), but all access like this should, by convention, be avoided.

    At request from a commenter, I'll add an example to shine light upon why we shouldn't really do anything with "the value that was passed as an in-out argument".

    struct MyStruct {
        var myStructsIntProperty: Int = 1
    
        mutating func myNotVeryThoughtThroughInoutFunction (inout myInt: Int) {
            myStructsIntProperty += 1
            /* What happens here? 'myInt' inout parameter is passed to this
               function by argument 'myStructsIntProperty' from _this_ instance
               of the MyStruct structure. Hence, we're trying to increase the
               value of the inout argument. Since the swift docs describe inout 
               as a "call by reference" type as well as a "copy-in-copy-out"
               method, this behaviour is somewhat undefined (at least avoidable).
    
               After the function has been called: will the value of
               myStructsIntProperty have been increased by 1 or 2? (answer: 1) */
            myInt += 1
        }
    
        func myInoutFunction (inout myInt: Int) {
            myInt += 1
        }
    }
    
    var a = MyStruct()
    print(a.myStructsIntProperty) // 1
    a.myInoutFunction(&a.myStructsIntProperty)
    print(a.myStructsIntProperty) // 2
    a.myNotVeryThoughtThroughInoutFunction(&a.myStructsIntProperty)
    print(a.myStructsIntProperty) // 3 or 4? prints 3.
    

    So, in this case, the inout behaves as copy-in-copy-out (and not by reference). We summarize by repeating the following statement from the language ref docs:

    Do not depend on the behavioral differences between copy-in copy-out and call by reference.

提交回复
热议问题