Why Does a Function Have Long-Term Write Access to All of Its In-Out Parameters

Memory safety of “+=” operator in Swift

I know you've got it, but a clarification for future readers; in your comments, you said:

... it is, in the end, a problem with any one-line code changing self by reading self first.

No, that alone is not sufficient. As the Memory Safety chapter says, this problem manifests itself only when:

  • At least one is a write access.
  • They access the same location in memory.
  • Their durations overlap.

Consider:

var foo = 41
foo = foo + 1

The foo = foo + 1 is not a problem (nor would foo += 1; nor would foo += foo) because constitutes a series of "instantaneous" accesses. So although we have (to use your phrase) "code changing self by reading self first", it is not a problem because their durations do not overlap.

The problem only manifests itself when you're dealing with "long-term" accesses. As that guide goes on to say:

A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.

One consequence of this long-term write access is that you can’t access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it—any access to the original creates a conflict.

So, consider your second example:

var stepSize = 1
func incrementInPlace(_ number: inout Int) {
number += stepSize
}
incrementInPlace(&stepSize)

In this case, you have a long-term access to whatever number references. When you invoke it with &stepSize, that means you have a long-term access to the memory associated with stepSize and thus number += stepSize means that you're trying to access stepSize while you already have a long-term access to it.

When to use inout parameters?

inout means that modifying the local variable will also modify the passed-in parameters. Without it, the passed-in parameters will remain the same value. Trying to think of reference type when you are using inout and value type without using it.

For example:

import UIKit

var num1: Int = 1
var char1: Character = "a"

func changeNumber(var num: Int) {
num = 2
print(num) // 2
print(num1) // 1
}
changeNumber(num1)

func changeChar(inout char: Character) {
char = "b"
print(char) // b
print(char1) // b
}
changeChar(&char1)

A good use case will be swap function that it will modify the passed-in parameters.

Swift 3+ Note: Starting in Swift 3, the inout keyword must come after the colon and before the type. For example, Swift 3+ now requires func changeChar(char: inout Character).

Swift: When i pass a parameter to a function does the function copies the parameter or uses the reference to that parameter

When you pass a reference type into a swift function you are passing along a reference to that object, so any changes that you make to the object in that method will effect that object outside the function. You can achieve a similar effect for value types if you label them as inout parameters.

Swift memory conflict where it should not happen

Short version: require AnyObject for ModelType.

Long version:

You're trying to read from self.model while you're in the middle of setting self.model. When you say "If the "update" code is moved into the "receiveValue" closure, the error will not occur," this isn't quite correct. I expect what you mean is you wrote this:

    cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(model.selected)")
})

And that worked, but that's completely different code. model in this case is the local variable, not the property self.model. You'll get the same crash if you write it this way:

    cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(self.model.selected)")
})

The path that gets you here is:

  • ViewModel.selected.didSet
  • WRITE to Model.selected <---
  • Model.selected.didSet
  • (observer-closure)
  • ViewModel.update
  • READ from ViewModel.model <---

This is a read and write to the same value, and that violates exclusive access. Note that the "value" in question is "the entire ViewModel value," not ViewModel.selected. You can show this by changing the update function to:

    print("update \(model!)")

You'll get the same crash.

So why does this work when you take out the generic? Because this particularly strict version of exclusivity only applies to value types (like structs). It doesn't apply to classes. So when this is concrete, Swift knows viewModel is a class, and that's ok. (The why behind this difference a bit complex, so I suggest reading the proposal that explains it.)

When you make this generic, Swift has to be very cautious. It doesn't know that Model is a class, so it applies stricter rules. You can fix this by promising that it's a class:

protocol ModelType: AnyObject { ... }

The use of IN OUT in Ada

An in parameter can be read but not written by the subprogram. in is the default. Prior to Ada 2012, functions were only allowed to have in parameters. The actual parameter is an expression.

An out parameter implies that the previous value is of no interest. The subprogram is expected to write to the parameter. After writing to the parameter, the subprogram can read back what it has written. On exit the actual parameter receives the value written to it (there are complications in this area!). The actual parameter must be a variable.

An in out parameter is like an out parameter except that the previous value is of interest and can be read by the subprogram before assignment. For example,

procedure Add (V : Integer; To : in out Integer; Limited_To : Integer)
is
begin
-- Check that the result wont be too large. This involves reading
-- the initial value of the 'in out' parameter To, which would be
-- wrong if To was a mere 'out' parameter (it would be
-- uninitialized).
if To + V > Limited_To then
To := Limited_To;
else
To := To + V;
end if;
end Add;


Related Topics



Leave a reply



Submit