Initialization of 'UnsafeMutableRawPointer' results in a dangling pointer - swift

Since Xcode 11.4 I get the warning message "Initialization of 'UnsafeMutableRawPointer' results in a dangling pointer"
for the following code where I read an SIMD4 from an MTLTexture into an array:
let texArray = Array<SIMD4<Float>>(repeating: SIMD4<Float>(repeating: 0), count: 1)
texture.getBytes(UnsafeMutableRawPointer(mutating: texArray), bytesPerRow: (MemoryLayout<SIMD4<Float>>.size * texture.width), from: region, mipmapLevel: 0)
Can somebody figure out how to create the array pointer to get rid of the warning ?
Thanks

TLDR
make text array mutable (use var instead of let) and use withUnsafeMutableBytes
var texArray = Array<SIMD4<Float>>(repeating: SIMD4<Float>(repeating: 0), count: 1)
texArray.withUnsafeMutableBytes { texArrayPtr in
texture.getBytes(texArrayPtr.baseAddress!, bytesPerRow: (MemoryLayout<SIMD4<Float>>.size * texture.width), from: region, mipmapLevel: 0)
}
Explanation
The warning was introduced because the compiler can't make sure that data backing the pointer will not be deallocated. Consider you have a function (e.g. implemented in C) manipulating some data pointed to.
func f(_ a: UnsafeMutablePointer<Int>){
a[0] = 42
}
Then it must be made sure that memory was not deallocated until the end of the call. So when calling this function in the following way it is not safe
var a: = [1]
p: UnsafeMutablePointer<Int>(&a)
// at this point the compiler may optimise and deallocate 'a' since it is not used anymore
f(p)
Currently this won't be an issue as far as I know since local variables will not be deallocated before the end of the scope.
One can illustrate the possible issue by introducing a nested scope in the following way
var p: UnsafeMutablePointer<Int>?
do {
var a = [1]
p = UnsafeMutablePointer<Int>(&a)
} // "a" will be deallocated here
// now "p" is a dangling pointer the compiler warned you of
var b = [0] // compiler will use same memory as for "a", so manipulating memory of "p" won't segfault
f(p!) // manipulate memory
print(b[0]) // prints 42 although "b" was initialised to 0
Due to the fact that b allocates the same memory that a was using before, the memory of b is modified by the call to f(p!). So b[0] is 42 although it was initialised to 0 and not explicitly modified.
With this illustration it should become clear why there are methods withUnsafeMutableBytes and withUnsafeMutableBufferPointer on Swift arrays and global functions withUnsafeMutablePointer plus immutable variants. (I personally find it confusing that methods must be used on arrays and and global functions on structs.)
These functions make sure that memory is not deallocated (or reused) for the scope of the closure (I also created gist with some examples).

Related

Swift Closures: Must Capture Lists Be Exhaustive?

Suppose I have a Swift class like this:
#objc final MyClass : NSObject
{
let classPropertyString = "A class property"
func doStuff()
{
let localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in
// Do things with 'classPropertyString' and 'localString'
}
}
}
My question is: when I write a capture list, am I responsible for EXHAUSTIVELY listing all the things to which I want the closure to hold a strong reference?
In other words, if I omit localString from my capture list (as I've done here), will the closure still automatically capture a strong reference to it or am I in for a bad time?
There are several minor quirks with your question that make it tricky to answer clearly, but I think I understand the underlying concern, and the short answer is "no." But your example is impossible, so the answer is "it's impossible." And if it were possible, there'd be no strong reference (nor would there be a need for one), so the question still would be kind of "it's impossible." Even so, let's walk through what's going on here.
First, closure can't reference localString unless it's reassigned somehow in the comment inside doStuff(). closure is assigned at a level where localString is not in scope. Closures can only capture variables that are in scope when they are assigned, not when they're called. But let's go back to the original version of this question, before it was edited. That version did have the case you're describing:
#objc final myClass : NSObject
{
let classPropertyString = "A class property"
func doStuff()
{
let localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in // (1)
// Do things with 'classPropertyString' and 'localString'
}
// (2)
}
}
There's no problems here. classPropertyString is copied into the closure, avoiding any retain loops. localString is referenced by the closure, and so it's preserved as long as the closure exists.
Because you listed classPropertyString in the capture list, it is evaluated at point (1) and copied into the closure. Because you implicitly captured localString, it is treated as a reference. See Capture Lists in the Swift Programming Language Reference for some excellent examples of exactly how this works in different cases.
In no case (*) will Swift allow the underlying storage for something you're using in a closure to disappear behind your back. That's why the typical concern is excessive retains (memory leaks) rather than dangling references (crashes).
(*) "In no case" here is a lie. There are several ways that Swift will allow it, but almost all of them involve "Unsafe" which is your warning about that. The major exception is unowned, and of course anything involving ! types. And Swift is not typically thread-safe, so you need to be careful about that...
The last comment about thread-safety is a place where the subtle distinctions between implicit and explicit captures can really matter. Consider this case where you modify an implicitly captured value on two queues:
func doStuff() -> String
{
var localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async {
localString = "something else"
callFunction(localString)
}
localString = "even more changes"
return localString
}
What happens in that case? Good grief, never do that. I believe it's undefined behavior and that localString could be anything including corrupted memory, at least in the most general case (it might be defined behavior for calling .async; I'm not sure). But don't do it.
But for your normal cases, there is no reason to explicitly capture local variables. (I sometimes wish Swift had gone the C++ way and said it was required, but it isn't.)
Ok, one more way implicit and explicit are different that might drive home how they work. Consider a stateful closure like this (I build these pretty often):
func incrementor() -> () -> Int {
var n = 0
return {
n += 1
return n
}
}
let inc = incrementor()
inc() // 1
inc() // 2
inc() // 3
let inc2 = incrementor()
inc2() // 1
See how the local variable n is captured by the closure, and can be modified after it goes out of scope. And see how inc2 has its own version of that local variable. Now try that with explicit capture.
func incrementor() -> () -> Int {
var n = 0
return { [n] in // <---- add [n]
n += 1 // Left side of mutating operator isn't mutable: 'n' is an immutable capture
return n
}
}
Explicit captures are copies and they're immutable. Implicit captures are references, and so have the same mutability as the thing they reference.

Test Copy-on-write in swift

import Foundation
func address(o:UnsafeRawPointer) -> Int {
return Int(bitPattern: o)
}
var originArray = [1,2,3]
var firstArray = originArray
//q.append(4)
print(NSString.init(format: "originArray:%p", address(o: &originArray)))
print(NSString.init(format: "firstArray:%p", address(o: &firstArray)))
Debug Log:
originArray:0x100b087b0
firstArray:0x100b088c0
Above, it's my test code.I think that I don't modify originArray which like appending or reduceing element. they should point same address.but why deference
Your code is printing the addresses of the array buffers (Array is a special case when passing a value to a pointer parameter). However, in Swift 3, the compiler assumed that the presence of the & operator meant that the buffer was being passed as mutable memory, so (unnecessarily) made it unique (by copying) before passing its pointer value, despite that pointer value being passed as an UnsafeRawPointer. That's why you see different addresses.
If you remove the & operator and pass the arrays directly:
func address(_ p: UnsafeRawPointer) {
print(p)
}
var originArray = [1, 2, 3]
var firstArray = originArray
address(originArray) // 0x00000000016e71c0
address(firstArray) // 0x00000000016e71c0
You'll now get the same addresses, as the compiler now assumes that address(_:) will not modify the memory of the buffers passed, as they're being passed to an UnsafeRawPointer parameter.
In Swift 4, this inconsistency is fixed, and the compiler no longer makes the buffer unique before passing its pointer values to an UnsafeRawPointer parameter, even when using the & operator, so your code exhibits expected behaviour.
Although, it's worth noting that the above method isn't guaranteed to produce stable pointer values when passing the same array to multiple pointer parameters.
From the Swift blog post "Interacting with C Pointers":
Even if you pass the same variable, array, or string as multiple pointer arguments, you could receive a different pointer each time.
I believe this guarantee cannot be met for arrays in two cases (there may be more):
If the array is viewing elements in non-contiguous storage
Swift's Array can view elements in non-contiguous storage, for example when it is wrapping an NSArray. In such a case, when passing it to a pointer parameter, a new contiguous buffer will have to be created, therefore giving you a different pointer value.
If the buffer is non-uniquely referenced when passed as mutable memory
As mentioned earlier, when passing an array to a mutable pointer parameter, its buffer will first be made unique in order to preserve value semantics, as it's assumed the function will perform a mutation of the buffer.
Therefore, if the buffer was copied, you'll get a different pointer value to if you had passed the array to an immutable pointer parameter.
Although neither of these two points are applicable in the example you give, it's worth bearing in mind that the compiler still doesn't guarantee you stable pointer values to the array's buffer when passing to pointer parameters.
For results that are guaranteed to be reliable, you should use the withUnsafeBytes(_:) method on a ContiguousArray:
var originArray: ContiguousArray = [1, 2, 3]
var firstArray = originArray
originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000102829550
firstArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000102829550
This is because withUnsafeBytes(_:) is documented as accepting:
A closure with an UnsafeRawBufferPointer parameter that points to the contiguous storage for the array. If no such storage exists, it is created.
And ContiguousArray guarantees that:
[it] always stores its elements in a contiguous region of memory
And just like Array, ContiguousArray uses copy-on-write in order to have value semantics, so you can still use it to check when the array's buffer is copied upon a mutation taking place:
var originArray: ContiguousArray = [1, 2, 3]
var firstArray = originArray
originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000103103eb0
firstArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000103103eb0
firstArray[0] = 4
originArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000103103eb0
firstArray.withUnsafeBytes { print($0.baseAddress!) } // 0x0000000100e764d0
You're printing the address of the variable itself, not the address of the array buffer it's pointing to.
You can get the address of the arrays' buffers like so:
var originArray = [1, 2, 3]
var firstArray = originArray
print("originArray: \(originArray.withUnsafeBytes { $0.baseAddress! })")
print("firstArray: \(firstArray.withUnsafeBytes { $0.baseAddress! })")
Now the same value is printed, unless you modify one of the arrays.

Swift function object wrapper in apple/swift

After reading:
https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift
https://github.com/rodionovd/SWRoute/blob/master/SWRoute/rd_get_func_impl.c
I understood that Swift function pointer is wrapped by swift_func_wrapper and swift_func_object (according to the article in 2014).
I guess this still works in Swift 3, but I couldn't find which file in https://github.com/apple/swift best describes these structs.
Can anyone help me?
I believe these details are mainly part of the implementation of Swift's IRGen – I don't think you'll find any friendly structs in the source showing you the full structure of various Swift function values. Therefore if you want to do some digging into this, I would recommend examining the IR emitted by the compiler.
You can do this by running the command:
xcrun swiftc -emit-ir main.swift | xcrun swift-demangle > main.irgen
which will emit the IR (with demangled symbols) for a -Onone build. You can find the documentation for LLVM IR here.
The following is some interesting stuff that I've been able to learn from going through the IR myself in a Swift 3.1 build. Note that this is all subject to change in future Swift versions (at least until Swift is ABI stable). It goes without saying that the code examples given below are only for demonstration purposes; and shouldn't ever be used in actual production code.
Thick function values
At a very basic level, function values in Swift are simple things – they're defined in the IR as:
%swift.function = type { i8*, %swift.refcounted* }
which is the raw function pointer i8*, along with a pointer to its context %swift.refcounted*, where %swift.refcounted is defined as:
%swift.refcounted = type { %swift.type*, i32, i32 }
which is the structure of a simple reference-counted object, containing a pointer to the object's metadata, along with two 32 bit values.
These two 32 bit values are used for the reference count of the object. Together , they can either represent (as of Swift 4):
The strong and unowned reference count of the object + some flags, including whether the object uses native Swift reference counting (as opposed to Obj-C reference counting), and whether the object has a side table.
or
A pointer to a side table, which contains the above, plus the weak reference count of the object (on forming a weak reference to an object, if it doesn't already have a side table, one will be created).
For further reading on the internals of Swift reference counting, Mike Ash has a great blog post on the subject.
The context of a function usually adds extra values onto the end of this %swift.refcounted structure. These values are dynamic things that the function needs upon being called (such as any values that it has captured, or any parameters that it has been partially applied with). In quite a few cases, function values won't need a context, so the pointer to the context will simply be nil.
When the function comes to be called, Swift will simply pass in the context as the last parameter. If the function doesn't have a context parameter, the calling convention appears to allow it to be safely passed anyway.
The storing of the function pointer along with the context pointer is called a thick function value, and is how Swift usually stores function values of known type (as opposed to a thin function value which is just the function pointer).
So, this explains why MemoryLayout<(Int) -> Int>.size returns 16 bytes – because it's made up of two pointers (each being a word in length, i.e 8 bytes on a 64 bit platform).
When thick function values are passed into function parameters (where those parameters are of non-generic type), Swift appears to pass the raw function pointer and context as separate parameters.
Capturing values
When a closure captures a value, this value will be put into a heap-allocated box (although the value itself can get stack-promoted in the case of a non-escaping closure – see later section). This box will be available to the function through the context object (the relevant IR).
For a closure that just captures a single value, Swift just makes the box itself the context of the function (no need for extra indirection). So you'll have a function value which looks like a ThickFunction<Box<T>> from the following structures:
// The structure of a %swift.function.
struct ThickFunction<Context> {
// the raw function pointer
var ptr: UnsafeRawPointer
// the context of the function value – can be nil to indicate
// that the function has no context.
var context: UnsafePointer<Context>?
}
// The structure of a %swift.refcounted.
struct RefCounted {
// pointer to the metadata of the object
var type: UnsafeRawPointer
// the reference counting bits.
var refCountingA: UInt32
var refCountingB: UInt32
}
// The structure of a %swift.refcounted, with a value tacked onto the end.
// This is what captured values get wrapped in (on the heap).
struct Box<T> {
var ref: RefCounted
var value: T
}
In fact, we can actually verify this for ourselves by running the following:
// this wrapper is necessary so that the function doesn't get put through a reabstraction
// thunk when getting typed as a generic type T (such as with .initialize(to:))
struct VoidVoidFunction {
var f: () -> Void
}
func makeClosure() -> () -> Void {
var i = 5
return { i += 2 }
}
let f = VoidVoidFunction(f: makeClosure())
let ptr = UnsafeMutablePointer<VoidVoidFunction>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: ThickFunction<Box<Int>>.self, capacity: 1) {
$0.pointee.context! // force unwrap as we know the function has a context object.
}
print(ctx.pointee)
// Box<Int>(ref:
// RefCounted(type: 0x00000001002b86d0, refCountingA: 2, refCountingB: 2),
// value: 5
// )
f.f() // call the closure – increment the captured value.
print(ctx.pointee)
// Box<Int>(ref:
// RefCounted(type: 0x00000001002b86d0, refCountingA: 2, refCountingB: 2),
// value: 7
// )
ptr.deinitialize()
ptr.deallocate(capacity: 1)
We can see that by calling the function between printing out the value of the context object, we can observe the changing in value of the captured variable i.
For multiple captured values, we need extra indirection, as the boxes cannot be stored directly as the given function's context, and may be captured by other closures. This is done by adding pointers to the boxes to the end of a %swift.refcounted.
For example:
struct TwoCaptureContext<T, U> {
// reference counting header
var ref: RefCounted
// pointers to boxes with captured values...
var first: UnsafePointer<Box<T>>
var second: UnsafePointer<Box<U>>
}
func makeClosure() -> () -> Void {
var i = 5
var j = "foo"
return { i += 2; j += "b" }
}
let f = VoidVoidFunction(f: makeClosure())
let ptr = UnsafeMutablePointer<VoidVoidFunction>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to:
ThickFunction<TwoCaptureContext<Int, String>>.self, capacity: 1) {
$0.pointee.context!.pointee
}
print(ctx.first.pointee.value, ctx.second.pointee.value) // 5 foo
f.f() // call the closure – mutate the captured values.
print(ctx.first.pointee.value, ctx.second.pointee.value) // 7 foob
ptr.deinitialize()
ptr.deallocate(capacity: 1)
Passing functions into parameters of generic type
You'll note that in the previous examples, we used a VoidVoidFunction wrapper for our function values. This is because otherwise, when being passed into a parameter of generic type (such as UnsafeMutablePointer's initialize(to:) method), Swift will put a function value through some reabstraction thunks in order to unify its calling convention to one where the arguments and return are passed by reference, rather than value (the relevant IR).
But now our function value has a pointer to the thunk, rather than the actual function we want to call. So how does the thunk know which function to call? The answer is simple – Swift puts the function that we want to the thunk to call in the context itself, which will therefore look like this:
// the context object for a reabstraction thunk – contains an actual function to call.
struct ReabstractionThunkContext<Context> {
// the standard reference counting header
var ref: RefCounted
// the thick function value for the thunk to call
var function: ThickFunction<Context>
}
The first thunk that we go through has 3 parameters:
A pointer to where the return value should be stored
A pointer to where the arguments for the function are located
The context object which contains the actual thick function value to call (such as shown above)
This first thunk just extracts the function value from the context, and then calls a second thunk, with 4 parameters:
A pointer to where the return value should be stored
A pointer to where the arguments for the function are located
The raw function pointer to call
The pointer to the context of the function to call
This thunk now retrieves the arguments (if any) from the argument pointer, then calls the given function pointer with these arguments, along with its context. It then stores the return value (if any) at the address of the return pointer.
Like in the previous examples, we can test this like so:
func makeClosure() -> () -> Void {
var i = 5
return { i += 2 }
}
func printSingleCapturedValue<T>(t: T) {
let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
ptr.initialize(to: t)
let ctx = ptr.withMemoryRebound(to:
ThickFunction<ReabstractionThunkContext<Box<Int>>>.self, capacity: 1) {
// get the context from the thunk function value, which we can
// then get the actual function value from, and therefore the actual
// context object.
$0.pointee.context!.pointee.function.context!
}
// print out captured value in the context object
print(ctx.pointee.value)
ptr.deinitialize()
ptr.deallocate(capacity: 1)
}
let closure = makeClosure()
printSingleCapturedValue(t: closure) // 5
closure()
printSingleCapturedValue(t: closure) // 7
Escaping vs. non-escaping capture
When the compiler can determine that the capture of a given local variable doesn't escape the lifetime of the function it's declared in, it can optimise by promoting the value of that variable from the heap-allocated box to the stack (this is a guaranteed optimisation, and occurs in even -Onone). Then, the function's context object need only store a pointer to the given captured value on the stack, as it is guaranteed not to be needed after the function exits.
This can therefore be done when the closure(s) capturing the variable are known not to escape the lifetime of the function.
Generally, an escaping closure is one that either:
Is stored in a non-local variable (including being returned from the function).
Is captured by another escaping closure.
Is passed as an argument to a function where that parameter is either marked as #escaping, or is not of function type (note this includes composite types, such as optional function types).
So, the following are examples where the capture of a given variable can be considered not to escape the lifetime of the function:
// the parameter is non-escaping, as is of function type and is not marked #escaping.
func nonEscaping(_ f: () -> Void) {
f()
}
func bar() -> String {
var str = ""
// c doesn't escape the lifetime of bar().
let c = {
str += "c called; "
}
c();
// immediately-evaluated closure obviously doesn't escape.
{ str += "immediately-evaluated closure called; " }()
// closure passed to non-escaping function parameter, so doesn't escape.
nonEscaping {
str += "closure passed to non-escaping parameter called."
}
return str
}
In this example, because str is only ever captured by closures that are known not to escape the lifetime of the function bar(), the compiler can optimise by storing the value of str on the stack, with the context objects storing only a pointer to it (the relevant IR).
So, the context objects for each of the closures1 will look like Box<UnsafePointer<String>>, with pointers to the string value on the stack. Although unfortunately, in a Schrödinger-like manner, attempting to observe this by allocating and re-binding a pointer (like before) triggers the compiler to treat the given closure as escaping – so we're once again looking at a Box<String> for the context.
In order to deal with the disparity between context objects that hold pointer(s) to the captured values rather than holding the values in their own heap-allocated boxes – Swift creates specialised implementations of the closures that take pointers to the captured values as arguments.
Then, a thunk is created for each closure that simply takes in a given context object, extracts the pointer(s) to the captured values from it, and passes this onto the specialised implementation of the closure. Now, we can just have a pointer to this thunk along with our context object as the thick function value.
For multiple captured values that don't escape, the additional pointers are simply added onto the end of the box, i.e
struct TwoNonEscapingCaptureContext<T, U> {
// reference counting header
var ref: RefCounted
// pointers to captured values (on the stack)...
var first: UnsafePointer<T>
var second: UnsafePointer<U>
}
This optimisation of promoting the captured values from the heap to the stack can be especially beneficial in this case, as we're no longer having to allocate separate boxes for each value – such as was the case previously.
Furthermore it's worth noting that lots of cases with non-escaping closure capture can be optimised much more aggressively in -O builds with inlining, which can result in context objects being optimised away entirely.
1. Immediately-evaluated closures actually don't use a context object, the pointer(s) to the captured values are just passed directly to it upon calling.

Struct cannot have a stored property that references itself [duplicate]

Reference cycles in Swift occur when properties of reference types have strong ownership of each other (or with closures).
Is there, however, a possibility of having reference cycles with value types only?
I tried this in playground without succes (Error: Recursive value type 'A' is not allowed).
struct A {
var otherA: A? = nil
init() {
otherA = A()
}
}
A reference cycle (or retain cycle) is so named because it indicates a cycle in the object graph:
Each arrow indicates one object retaining another (a strong reference). Unless the cycle is broken, the memory for these objects will never be freed.
When capturing and storing value types (structs and enums), there is no such thing as a reference. Values are copied, rather than referenced, although values can hold references to objects.
In other words, values can have outgoing arrows in the object graph, but no incoming arrows. That means they can't participate in a cycle.
As the compiler told you, what you're trying to do is illegal. Exactly because this is a value type, there's no coherent, efficient way to implement what you're describing. If a type needs to refer to itself (e.g., it has a property that is of the same type as itself), use a class, not a struct.
Alternatively, you can use an enum, but only in a special, limited way: an enum case's associated value can be an instance of that enum, provided the case (or the entire enum) is marked indirect:
enum Node {
case None(Int)
indirect case left(Int, Node)
indirect case right(Int, Node)
indirect case both(Int, Node, Node)
}
Disclaimer: I'm making an (hopefully educated) guess about the inner workings of the Swift compiler here, so apply grains of salt.
Aside from value semantics, ask yourself: Why do we have structs? What is the advantage?
One advantage is that we can (read: want to) store them on the stack (resp. in an object frame), i.e. just like primitive values in other languages. In particular, we don't want to allocate dedicated space on the heap to point to. That makes accessing struct values more efficient: we (read: the compiler) always knows where exactly in memory it finds the value, relative to the current frame or object pointer.
In order for that to work out for the compiler, it needs to know how much space to reserve for a given struct value when determining the structure of the stack or object frame. As long as struct values are trees of fixed size (disregarding outgoing references to objects; they point to the heap are not of interest for us), that is fine: the compiler can just add up all the sizes it finds.
If you had a recursive struct, this fails: you can implement lists or binary trees in this way. The compiler can not figure out statically how to store such values in memory, so we have to forbid them.
Nota bene: The same reasoning explains why structs are pass-by-value: we need them to physically be at their new context.
Quick and easy hack workaround: just embed it in an array.
struct A {
var otherA: [A]? = nil
init() {
otherA = [A()]
}
}
You normally cannot have a reference cycle with value types simply because Swift normally doesn't allow references to value types. Everything is copied.
However, if you're curious, you actually can induce a value-type reference cycle by capturing self in a closure.
The following is an example. Note that the MyObject class is present merely to illustrate the leak.
class MyObject {
static var objCount = 0
init() {
MyObject.objCount += 1
print("Alloc \(MyObject.objCount)")
}
deinit {
print("Dealloc \(MyObject.objCount)")
MyObject.objCount -= 1
}
}
struct MyValueType {
var closure: (() -> ())?
var obj = MyObject()
init(leakMe: Bool) {
if leakMe {
closure = { print("\(self)") }
}
}
}
func test(leakMe leakMe: Bool) {
print("Creating value type. Leak:\(leakMe)")
let _ = MyValueType(leakMe: leakMe)
}
test(leakMe: true)
test(leakMe: false)
Output:
Creating value type. Leak:true
Alloc 1
Creating value type. Leak:false
Alloc 2
Dealloc 2
Is there, however, a possibility of having reference cycles with value types only?
Depends on what you mean with "value types only".
If you mean completely no reference including hidden ones inside, then the answer is NO. To make a reference cycle, you need at least one reference.
But in Swift, Array, String or some other types are value types, which may contain references inside their instances. If your "value types" includes such types, the answer is YES.

Reference cycles with value types?

Reference cycles in Swift occur when properties of reference types have strong ownership of each other (or with closures).
Is there, however, a possibility of having reference cycles with value types only?
I tried this in playground without succes (Error: Recursive value type 'A' is not allowed).
struct A {
var otherA: A? = nil
init() {
otherA = A()
}
}
A reference cycle (or retain cycle) is so named because it indicates a cycle in the object graph:
Each arrow indicates one object retaining another (a strong reference). Unless the cycle is broken, the memory for these objects will never be freed.
When capturing and storing value types (structs and enums), there is no such thing as a reference. Values are copied, rather than referenced, although values can hold references to objects.
In other words, values can have outgoing arrows in the object graph, but no incoming arrows. That means they can't participate in a cycle.
As the compiler told you, what you're trying to do is illegal. Exactly because this is a value type, there's no coherent, efficient way to implement what you're describing. If a type needs to refer to itself (e.g., it has a property that is of the same type as itself), use a class, not a struct.
Alternatively, you can use an enum, but only in a special, limited way: an enum case's associated value can be an instance of that enum, provided the case (or the entire enum) is marked indirect:
enum Node {
case None(Int)
indirect case left(Int, Node)
indirect case right(Int, Node)
indirect case both(Int, Node, Node)
}
Disclaimer: I'm making an (hopefully educated) guess about the inner workings of the Swift compiler here, so apply grains of salt.
Aside from value semantics, ask yourself: Why do we have structs? What is the advantage?
One advantage is that we can (read: want to) store them on the stack (resp. in an object frame), i.e. just like primitive values in other languages. In particular, we don't want to allocate dedicated space on the heap to point to. That makes accessing struct values more efficient: we (read: the compiler) always knows where exactly in memory it finds the value, relative to the current frame or object pointer.
In order for that to work out for the compiler, it needs to know how much space to reserve for a given struct value when determining the structure of the stack or object frame. As long as struct values are trees of fixed size (disregarding outgoing references to objects; they point to the heap are not of interest for us), that is fine: the compiler can just add up all the sizes it finds.
If you had a recursive struct, this fails: you can implement lists or binary trees in this way. The compiler can not figure out statically how to store such values in memory, so we have to forbid them.
Nota bene: The same reasoning explains why structs are pass-by-value: we need them to physically be at their new context.
Quick and easy hack workaround: just embed it in an array.
struct A {
var otherA: [A]? = nil
init() {
otherA = [A()]
}
}
You normally cannot have a reference cycle with value types simply because Swift normally doesn't allow references to value types. Everything is copied.
However, if you're curious, you actually can induce a value-type reference cycle by capturing self in a closure.
The following is an example. Note that the MyObject class is present merely to illustrate the leak.
class MyObject {
static var objCount = 0
init() {
MyObject.objCount += 1
print("Alloc \(MyObject.objCount)")
}
deinit {
print("Dealloc \(MyObject.objCount)")
MyObject.objCount -= 1
}
}
struct MyValueType {
var closure: (() -> ())?
var obj = MyObject()
init(leakMe: Bool) {
if leakMe {
closure = { print("\(self)") }
}
}
}
func test(leakMe leakMe: Bool) {
print("Creating value type. Leak:\(leakMe)")
let _ = MyValueType(leakMe: leakMe)
}
test(leakMe: true)
test(leakMe: false)
Output:
Creating value type. Leak:true
Alloc 1
Creating value type. Leak:false
Alloc 2
Dealloc 2
Is there, however, a possibility of having reference cycles with value types only?
Depends on what you mean with "value types only".
If you mean completely no reference including hidden ones inside, then the answer is NO. To make a reference cycle, you need at least one reference.
But in Swift, Array, String or some other types are value types, which may contain references inside their instances. If your "value types" includes such types, the answer is YES.