Swift 4, Simultaneous Access to Tuple Members as Inout

Swift 4, Simultaneous access to tuple members as inout

Simultaneous access is a new Swift 4 issue. Watch the WWDC video discussion of exclusive access, which is now being enforced. (Or read the proposal on swift-evolution.)

The exclusive access rule is there to prevent you exactly from doing what you're trying to do. You must not use inout to access things like multiple elements of an Array, a struct and its property, or (as you have discovered) the elements of a tuple — because the results can be incoherent.

Thus, this would be legal:

var Ints = (first:2, second:3);
var i = 4
swapInts(&Ints.first, &i);

But what you have is not:

var Ints = (first:2, second:3);
swapInts(&Ints.first, &Ints.second);

This case is pretty simple, though; there is no need to pass both elements of the tuple inout to anything. If you just want to swap elements, why not just say

(Ints.first, Ints.second) = (Ints.second, Ints.first)

EDIT But the issue can be reproduced only on the command line, not in an iOS project, and as Martin R points out in a comment, this doesn't seem to be a case of simultaneous access at all. I have filed a bug report.

EDIT2 My bug report came back as not being a bug. Global tuples really are different from local tuples.

EDIT3 The situation has completely changed in beta 5, where the test code doesn't even compile; the exclusive access problem is now caught up front.

Swift4: why and when is simultaneous access to separate struct elements illegal?

As you correctly state, in practice there is no conflicting access in your code. The question is whether Swift recognises that or plays safe.

Turning to The Swift Programming Language (Swift 4.2), which is as close as we can get to a formal definition of Swift we find in the Memory Safety chapter:

Conflicting Access to Properties

Types like structures, tuples, and enumerations are made up of individual constituent values, such as the properties of a structure or the elements of a tuple. Because these are value types, mutating any piece of the value mutates the whole value, meaning read or write access to one of the properties requires read or write access to the whole value.

Here you can read "Because these are value types" as "In Swift it was decided that for composite values type", i.e. Apple made a choice and defined things this way, other languages might make other choices.

So by that statement your code is conflicting. However a few paragraphs later an Apple writes about relaxing this specification:

In practice, most access to the properties of a structure can overlap safely.

[An example similar to yours except it uses a local variable]

The compiler can prove that memory safety is preserved because the two stored properties don’t interact in any way.

So Apple is saying in the case of local variables the compiler can determine there is no overlapping access and relax the restriction that access to any member is treated as access to the whole.

But you are using an instance variable of a class. A few paragraphs later Apple states:

Specifically, it can prove that overlapping access to properties of a structure is safe if the following conditions apply:

  • You’re accessing only stored properties of an instance, not computed properties or class properties.

  • The structure is the value of a local variable, not a global variable.

  • The structure is either not captured by any closures, or it’s captured only by nonescaping closures.

And from your code we can say that it appears ", not a global variable" here is not exhaustive, it means anything other than "a local variable".

Of course I say it appears here as we all know that Swift is a loosely defined moving target and its compiler and semantics will probably be different next Tuesday ;-)

HTH

Return multiple values from a function in swift

Return a tuple:

func getTime() -> (Int, Int, Int) {
...
return ( hour, minute, second)
}

Then it's invoked as:

let (hour, minute, second) = getTime()

or:

let time = getTime()
println("hour: \(time.0)")

Assigning values to tuple in for loop

There's no official API for doing this, but IIRC, tuples of homogeneous element types are guaranteed to have a contiguous memory layout. You take advantage of this by using UnsafeBufferPointer to read/write to the tuple.

Usually this requires you to manually hard-code the tuple's element count, but I wrote some helper functions that can do this for you. There's two variants, a mutable one, which lets you obtain an UnsafeBufferPointer, which you can read (e.g. to create an Array), and a mutable one, which gives you a UnsafeMutableBufferPointer, through which you can assign elements.

enum Tuple {
static func withUnsafeBufferPointer<Tuple, TupleElement, Result>(
to value: Tuple,
element: TupleElement.Type,
_ body: (UnsafeBufferPointer<TupleElement>) throws -> Result
) rethrows -> Result {
try withUnsafePointer(to: value) { tuplePtr in
let count = MemoryLayout<Tuple>.size / MemoryLayout<TupleElement>.size

return try tuplePtr.withMemoryRebound(
to: TupleElement.self,
capacity: count
) { elementPtr in
try body(UnsafeBufferPointer(start: elementPtr, count: count))
}

}
}

static func withUnsafeMutableBufferPointer<Tuple, TupleElement, Result>(
to value: inout Tuple,
element: TupleElement.Type,
_ body: (UnsafeMutableBufferPointer<TupleElement>) throws -> Result
) rethrows -> Result {
try withUnsafeMutablePointer(to: &value) { tuplePtr in
let count = MemoryLayout<Tuple>.size / MemoryLayout<TupleElement>.size

return try tuplePtr.withMemoryRebound(
to: TupleElement.self,
capacity: count
) { elementPtr in
try body(UnsafeMutableBufferPointer(start: elementPtr, count: count))
}

}
}
}

var destinationTouple: (Int, Int, Int, Int) = (0, 0, 0, 0) // => (0, 0, 0, 0)
var sourceArray = Array(1...4)

print("before:", destinationTouple)

Tuple.withUnsafeMutableBufferPointer(to: &destinationTouple, element: Int.self) { (destBuffer: UnsafeMutableBufferPointer<Int>) -> Void in
sourceArray.withUnsafeMutableBufferPointer { sourceBuffer in
// buffer[...] = 1...
destBuffer[destBuffer.indices] = sourceBuffer[destBuffer.indices]
return ()
}
}

print("after:", destinationTouple) // => (1, 2, 3, 4)

Can I reduce the amount of duplicated Swift code when passing an array of tuples into a function?

You can use a typealias, which is named exactly after what it does: an alias for a type:

typealias AgentTuple = (coverName: String, realName: String, accessLevel: Int, compromised: Bool)

func findCleanAgents(agents: [AgentTuple]) -> [AgentTuple] {
var cleanAgents: [AgentTuple] = []
for agent in agents {
if agent.compromised == false {
print("\(agent.coverName) isn't compromised.")
cleanAgents.append(agent)
}
}
}

Here's Swift's documentation

Tuples are best suited for a function that returns multiple values. They are not really convenient to pass around.

In your case, if these four data types typically go together, consider creating a new type:

struct Agent {
var coverName: String
var realName: String
var accessLevel: Int
var compromised: Bool
}

let agent = Agent(coverName: "Arlington Beech", realName: "James Bond", ...)

How do I add a tuple to a Swift Array?

Since this is still the top answer on google for adding tuples to an array, its worth noting that things have changed slightly in the latest release. namely:

when declaring/instantiating arrays; the type is now nested within the braces:

var stuff:[(name: String, value: Int)] = []

the compound assignment operator, +=, is now used for concatenating arrays; if adding a single item, it needs to be nested in an array:

stuff += [(name: "test 1", value: 1)]

it also worth noting that when using append() on an array containing named tuples, you can provide each property of the tuple you're adding as an argument to append():

stuff.append((name: "test 2", value: 2))

How do I assign outputs of a function with multiple outputs to different varables in Swift?

I believe you are talking about tuples here. Here is an example.

func blah(x: Int, y: Int) -> (a: Int, b: Int) {
return (x,y)
}

let output = blah(1,1)

print(output.0, output.b) // You can access them using indices or the variable names. If they don't have names, you'll have to use indices.

And about passing the tuple as input, it was deprecated in Swift 2 something and later removed in Swift 3. So you have no other way than to pass the parameters individually.

blah(output.a, output.b)

Or you can even use multiple variables as shown in @vacawama answer.



Related Topics



Leave a reply



Submit